From cf514b5782e438ac90f0857bdac9915c2b0a58a1 Mon Sep 17 00:00:00 2001 From: Apertis CI <devel@lists.apertis.org> Date: Wed, 26 Feb 2025 16:01:27 +0000 Subject: [PATCH] Import Upstream version 1.26.3 --- .hgtags | 483 ++++ CHANGES | 301 ++- CHANGES.ru | 310 ++- LICENSE | 2 +- auto/cc/conf | 2 +- auto/cc/msvc | 19 +- auto/install | 2 +- auto/lib/geoip/conf | 17 + auto/lib/google-perftools/conf | 16 + auto/lib/libatomic/conf | 6 +- auto/lib/libatomic/make | 11 +- auto/lib/libgd/conf | 17 + auto/lib/openssl/conf | 63 +- auto/lib/openssl/make | 15 +- auto/lib/openssl/makefile.msvc | 2 +- auto/lib/pcre/conf | 16 + auto/lib/pcre/make | 3 +- auto/make | 5 +- auto/modules | 109 +- auto/options | 15 + auto/os/conf | 17 +- auto/os/linux | 63 + auto/os/win32 | 2 +- auto/sources | 5 +- auto/unix | 48 + configure | 1 + contrib/vim/syntax/nginx.vim | 1173 +++------ src/core/nginx.c | 56 +- src/core/nginx.h | 4 +- src/core/ngx_bpf.c | 143 ++ src/core/ngx_bpf.h | 43 + src/core/ngx_conf_file.c | 4 +- src/core/ngx_connection.c | 85 +- src/core/ngx_connection.h | 10 +- src/core/ngx_core.h | 7 + src/core/ngx_hash.h | 7 +- src/core/ngx_inet.c | 2 +- src/core/ngx_module.h | 5 + src/core/ngx_output_chain.c | 10 +- src/core/ngx_proxy_protocol.c | 212 +- src/core/ngx_proxy_protocol.h | 6 +- src/core/ngx_queue.c | 52 +- src/core/ngx_queue.h | 3 + src/core/ngx_regex.c | 13 +- src/core/ngx_resolver.c | 61 +- src/core/ngx_resolver.h | 4 +- src/core/ngx_string.c | 7 +- src/core/ngx_string.h | 6 +- src/core/ngx_syslog.c | 52 +- src/core/ngx_syslog.h | 22 +- src/event/modules/ngx_iocp_module.c | 379 +++ src/event/modules/ngx_iocp_module.h | 22 + src/event/ngx_event.c | 43 +- src/event/ngx_event.h | 7 +- src/event/ngx_event_acceptex.c | 229 ++ src/event/ngx_event_connect.c | 2 + src/event/ngx_event_connectex.c | 206 ++ src/event/ngx_event_openssl.c | 614 ++++- src/event/ngx_event_openssl.h | 50 +- src/event/ngx_event_openssl_stapling.c | 2 +- src/event/ngx_event_pipe.c | 8 +- src/event/ngx_event_udp.c | 105 +- src/event/ngx_event_udp.h | 66 + src/event/quic/bpf/bpfgen.sh | 113 + src/event/quic/bpf/makefile | 30 + .../quic/bpf/ngx_quic_reuseport_helper.c | 140 ++ src/event/quic/ngx_event_quic.c | 1452 +++++++++++ src/event/quic/ngx_event_quic.h | 129 + src/event/quic/ngx_event_quic_ack.c | 1188 +++++++++ src/event/quic/ngx_event_quic_ack.h | 30 + src/event/quic/ngx_event_quic_bpf.c | 657 +++++ src/event/quic/ngx_event_quic_bpf_code.c | 88 + src/event/quic/ngx_event_quic_connection.h | 305 +++ src/event/quic/ngx_event_quic_connid.c | 502 ++++ src/event/quic/ngx_event_quic_connid.h | 29 + src/event/quic/ngx_event_quic_frames.c | 895 +++++++ src/event/quic/ngx_event_quic_frames.h | 45 + src/event/quic/ngx_event_quic_migration.c | 1003 ++++++++ src/event/quic/ngx_event_quic_migration.h | 45 + .../quic/ngx_event_quic_openssl_compat.c | 652 +++++ .../quic/ngx_event_quic_openssl_compat.h | 59 + src/event/quic/ngx_event_quic_output.c | 1319 ++++++++++ src/event/quic/ngx_event_quic_output.h | 40 + src/event/quic/ngx_event_quic_protection.c | 1243 +++++++++ src/event/quic/ngx_event_quic_protection.h | 120 + src/event/quic/ngx_event_quic_socket.c | 237 ++ src/event/quic/ngx_event_quic_socket.h | 28 + src/event/quic/ngx_event_quic_ssl.c | 595 +++++ src/event/quic/ngx_event_quic_ssl.h | 19 + src/event/quic/ngx_event_quic_streams.c | 1828 ++++++++++++++ src/event/quic/ngx_event_quic_streams.h | 44 + src/event/quic/ngx_event_quic_tokens.c | 309 +++ src/event/quic/ngx_event_quic_tokens.h | 34 + src/event/quic/ngx_event_quic_transport.c | 2215 +++++++++++++++++ src/event/quic/ngx_event_quic_transport.h | 397 +++ src/event/quic/ngx_event_quic_udp.c | 420 ++++ src/http/modules/ngx_http_access_module.c | 2 +- src/http/modules/ngx_http_auth_basic_module.c | 1 + .../modules/ngx_http_auth_request_module.c | 12 +- src/http/modules/ngx_http_dav_module.c | 1 + src/http/modules/ngx_http_fastcgi_module.c | 79 +- src/http/modules/ngx_http_flv_module.c | 3 +- src/http/modules/ngx_http_geo_module.c | 14 +- src/http/modules/ngx_http_geoip_module.c | 14 +- src/http/modules/ngx_http_grpc_module.c | 89 +- .../modules/ngx_http_gunzip_filter_module.c | 18 +- .../modules/ngx_http_gzip_filter_module.c | 31 +- .../modules/ngx_http_gzip_static_module.c | 4 + .../modules/ngx_http_headers_filter_module.c | 55 +- src/http/modules/ngx_http_memcached_module.c | 1 + src/http/modules/ngx_http_mp4_module.c | 35 +- src/http/modules/ngx_http_proxy_module.c | 104 +- .../modules/ngx_http_range_filter_module.c | 16 + src/http/modules/ngx_http_realip_module.c | 7 +- src/http/modules/ngx_http_referer_module.c | 2 +- src/http/modules/ngx_http_rewrite_module.c | 1 + src/http/modules/ngx_http_scgi_module.c | 63 +- src/http/modules/ngx_http_ssi_filter_module.c | 39 +- src/http/modules/ngx_http_ssi_filter_module.h | 1 + src/http/modules/ngx_http_ssl_module.c | 218 +- src/http/modules/ngx_http_ssl_module.h | 5 - src/http/modules/ngx_http_static_module.c | 10 +- src/http/modules/ngx_http_sub_filter_module.c | 8 +- .../modules/ngx_http_userid_filter_module.c | 19 +- src/http/modules/ngx_http_uwsgi_module.c | 146 +- src/http/modules/perl/nginx.xs | 116 +- src/http/ngx_http.c | 93 +- src/http/ngx_http.h | 20 +- src/http/ngx_http_copy_filter_module.c | 45 +- src/http/ngx_http_core_module.c | 222 +- src/http/ngx_http_core_module.h | 14 +- src/http/ngx_http_file_cache.c | 103 +- src/http/ngx_http_huff_decode.c | 6 +- src/http/ngx_http_parse.c | 63 +- src/http/ngx_http_request.c | 250 +- src/http/ngx_http_request.h | 20 +- src/http/ngx_http_request_body.c | 28 + src/http/ngx_http_script.c | 1 + src/http/ngx_http_special_response.c | 1 + src/http/ngx_http_upstream.c | 449 +++- src/http/ngx_http_upstream.h | 12 +- src/http/ngx_http_variables.c | 195 +- src/http/ngx_http_variables.h | 5 +- src/http/ngx_http_write_filter_module.c | 11 +- src/http/v2/ngx_http_v2.c | 410 +-- src/http/v2/ngx_http_v2.h | 34 +- src/http/v2/ngx_http_v2_filter_module.c | 631 +---- src/http/v2/ngx_http_v2_module.c | 124 +- src/http/v2/ngx_http_v2_module.h | 17 - src/http/v3/ngx_http_v3.c | 111 + src/http/v3/ngx_http_v3.h | 160 ++ src/http/v3/ngx_http_v3_encode.c | 304 +++ src/http/v3/ngx_http_v3_encode.h | 34 + src/http/v3/ngx_http_v3_filter_module.c | 852 +++++++ src/http/v3/ngx_http_v3_module.c | 393 +++ src/http/v3/ngx_http_v3_parse.c | 1936 ++++++++++++++ src/http/v3/ngx_http_v3_parse.h | 146 ++ src/http/v3/ngx_http_v3_request.c | 1718 +++++++++++++ src/http/v3/ngx_http_v3_table.c | 715 ++++++ src/http/v3/ngx_http_v3_table.h | 58 + src/http/v3/ngx_http_v3_uni.c | 622 +++++ src/http/v3/ngx_http_v3_uni.h | 34 + src/mail/ngx_mail_core_module.c | 51 +- src/mail/ngx_mail_handler.c | 6 +- src/mail/ngx_mail_proxy_module.c | 16 +- src/mail/ngx_mail_ssl_module.c | 60 +- src/mail/ngx_mail_ssl_module.h | 1 - src/os/unix/ngx_darwin_init.c | 16 +- src/os/unix/ngx_errno.h | 1 + src/os/unix/ngx_files.c | 4 + src/os/unix/ngx_linux_config.h | 4 + src/os/unix/ngx_linux_sendfile_chain.c | 1 + src/os/unix/ngx_posix_init.c | 5 +- src/os/unix/ngx_process_cycle.c | 21 +- src/os/unix/ngx_readv_chain.c | 6 +- src/os/unix/ngx_recv.c | 4 +- src/os/unix/ngx_socket.h | 2 + src/os/unix/ngx_udp_sendmsg_chain.c | 226 +- src/os/win32/nginx.ico | Bin 0 -> 1350 bytes src/os/win32/nginx.rc | 6 + src/os/win32/nginx_icon16.xpm | 24 + src/os/win32/nginx_icon32.xpm | 39 + src/os/win32/nginx_icon48.xpm | 55 + src/os/win32/ngx_alloc.c | 44 + src/os/win32/ngx_alloc.h | 27 + src/os/win32/ngx_atomic.h | 69 + src/os/win32/ngx_dlopen.c | 22 + src/os/win32/ngx_dlopen.h | 32 + src/os/win32/ngx_errno.c | 60 + src/os/win32/ngx_errno.h | 72 + src/os/win32/ngx_event_log.c | 99 + src/os/win32/ngx_files.c | 1459 +++++++++++ src/os/win32/ngx_files.h | 272 ++ src/os/win32/ngx_os.h | 68 + src/os/win32/ngx_process.c | 238 ++ src/os/win32/ngx_process.h | 80 + src/os/win32/ngx_process_cycle.c | 1044 ++++++++ src/os/win32/ngx_process_cycle.h | 44 + src/os/win32/ngx_service.c | 134 + src/os/win32/ngx_shmem.c | 161 ++ src/os/win32/ngx_shmem.h | 33 + src/os/win32/ngx_socket.c | 49 + src/os/win32/ngx_socket.h | 255 ++ src/os/win32/ngx_stat.c | 34 + src/os/win32/ngx_thread.c | 30 + src/os/win32/ngx_thread.h | 27 + src/os/win32/ngx_time.c | 83 + src/os/win32/ngx_time.h | 51 + src/os/win32/ngx_udp_wsarecv.c | 149 ++ src/os/win32/ngx_user.c | 23 + src/os/win32/ngx_user.h | 25 + src/os/win32/ngx_win32_config.h | 291 +++ src/os/win32/ngx_win32_init.c | 329 +++ src/os/win32/ngx_wsarecv.c | 215 ++ src/os/win32/ngx_wsarecv_chain.c | 147 ++ src/os/win32/ngx_wsasend.c | 185 ++ src/os/win32/ngx_wsasend_chain.c | 296 +++ src/stream/ngx_stream.c | 844 +++++-- src/stream/ngx_stream.h | 177 +- src/stream/ngx_stream_access_module.c | 2 +- src/stream/ngx_stream_core_module.c | 746 ++++-- src/stream/ngx_stream_geo_module.c | 8 +- src/stream/ngx_stream_geoip_module.c | 2 +- src/stream/ngx_stream_handler.c | 10 +- src/stream/ngx_stream_pass_module.c | 327 +++ src/stream/ngx_stream_proxy_module.c | 97 +- src/stream/ngx_stream_ssl_module.c | 440 +++- src/stream/ngx_stream_ssl_module.h | 7 +- src/stream/ngx_stream_ssl_preread_module.c | 57 +- src/stream/ngx_stream_variables.c | 62 + src/stream/ngx_stream_write_filter_module.c | 2 +- 231 files changed, 38197 insertions(+), 3710 deletions(-) create mode 100644 .hgtags create mode 100644 src/core/ngx_bpf.c create mode 100644 src/core/ngx_bpf.h create mode 100644 src/event/modules/ngx_iocp_module.c create mode 100644 src/event/modules/ngx_iocp_module.h create mode 100644 src/event/ngx_event_acceptex.c create mode 100644 src/event/ngx_event_connectex.c create mode 100644 src/event/ngx_event_udp.h create mode 100644 src/event/quic/bpf/bpfgen.sh create mode 100644 src/event/quic/bpf/makefile create mode 100644 src/event/quic/bpf/ngx_quic_reuseport_helper.c create mode 100644 src/event/quic/ngx_event_quic.c create mode 100644 src/event/quic/ngx_event_quic.h create mode 100644 src/event/quic/ngx_event_quic_ack.c create mode 100644 src/event/quic/ngx_event_quic_ack.h create mode 100644 src/event/quic/ngx_event_quic_bpf.c create mode 100644 src/event/quic/ngx_event_quic_bpf_code.c create mode 100644 src/event/quic/ngx_event_quic_connection.h create mode 100644 src/event/quic/ngx_event_quic_connid.c create mode 100644 src/event/quic/ngx_event_quic_connid.h create mode 100644 src/event/quic/ngx_event_quic_frames.c create mode 100644 src/event/quic/ngx_event_quic_frames.h create mode 100644 src/event/quic/ngx_event_quic_migration.c create mode 100644 src/event/quic/ngx_event_quic_migration.h create mode 100644 src/event/quic/ngx_event_quic_openssl_compat.c create mode 100644 src/event/quic/ngx_event_quic_openssl_compat.h create mode 100644 src/event/quic/ngx_event_quic_output.c create mode 100644 src/event/quic/ngx_event_quic_output.h create mode 100644 src/event/quic/ngx_event_quic_protection.c create mode 100644 src/event/quic/ngx_event_quic_protection.h create mode 100644 src/event/quic/ngx_event_quic_socket.c create mode 100644 src/event/quic/ngx_event_quic_socket.h create mode 100644 src/event/quic/ngx_event_quic_ssl.c create mode 100644 src/event/quic/ngx_event_quic_ssl.h create mode 100644 src/event/quic/ngx_event_quic_streams.c create mode 100644 src/event/quic/ngx_event_quic_streams.h create mode 100644 src/event/quic/ngx_event_quic_tokens.c create mode 100644 src/event/quic/ngx_event_quic_tokens.h create mode 100644 src/event/quic/ngx_event_quic_transport.c create mode 100644 src/event/quic/ngx_event_quic_transport.h create mode 100644 src/event/quic/ngx_event_quic_udp.c create mode 100644 src/http/v3/ngx_http_v3.c create mode 100644 src/http/v3/ngx_http_v3.h create mode 100644 src/http/v3/ngx_http_v3_encode.c create mode 100644 src/http/v3/ngx_http_v3_encode.h create mode 100644 src/http/v3/ngx_http_v3_filter_module.c create mode 100644 src/http/v3/ngx_http_v3_module.c create mode 100644 src/http/v3/ngx_http_v3_parse.c create mode 100644 src/http/v3/ngx_http_v3_parse.h create mode 100644 src/http/v3/ngx_http_v3_request.c create mode 100644 src/http/v3/ngx_http_v3_table.c create mode 100644 src/http/v3/ngx_http_v3_table.h create mode 100644 src/http/v3/ngx_http_v3_uni.c create mode 100644 src/http/v3/ngx_http_v3_uni.h create mode 100644 src/os/win32/nginx.ico create mode 100644 src/os/win32/nginx.rc create mode 100644 src/os/win32/nginx_icon16.xpm create mode 100644 src/os/win32/nginx_icon32.xpm create mode 100644 src/os/win32/nginx_icon48.xpm create mode 100644 src/os/win32/ngx_alloc.c create mode 100644 src/os/win32/ngx_alloc.h create mode 100644 src/os/win32/ngx_atomic.h create mode 100644 src/os/win32/ngx_dlopen.c create mode 100644 src/os/win32/ngx_dlopen.h create mode 100644 src/os/win32/ngx_errno.c create mode 100644 src/os/win32/ngx_errno.h create mode 100644 src/os/win32/ngx_event_log.c create mode 100644 src/os/win32/ngx_files.c create mode 100644 src/os/win32/ngx_files.h create mode 100644 src/os/win32/ngx_os.h create mode 100644 src/os/win32/ngx_process.c create mode 100644 src/os/win32/ngx_process.h create mode 100644 src/os/win32/ngx_process_cycle.c create mode 100644 src/os/win32/ngx_process_cycle.h create mode 100644 src/os/win32/ngx_service.c create mode 100644 src/os/win32/ngx_shmem.c create mode 100644 src/os/win32/ngx_shmem.h create mode 100644 src/os/win32/ngx_socket.c create mode 100644 src/os/win32/ngx_socket.h create mode 100644 src/os/win32/ngx_stat.c create mode 100644 src/os/win32/ngx_thread.c create mode 100644 src/os/win32/ngx_thread.h create mode 100644 src/os/win32/ngx_time.c create mode 100644 src/os/win32/ngx_time.h create mode 100644 src/os/win32/ngx_udp_wsarecv.c create mode 100644 src/os/win32/ngx_user.c create mode 100644 src/os/win32/ngx_user.h create mode 100644 src/os/win32/ngx_win32_config.h create mode 100644 src/os/win32/ngx_win32_init.c create mode 100644 src/os/win32/ngx_wsarecv.c create mode 100644 src/os/win32/ngx_wsarecv_chain.c create mode 100644 src/os/win32/ngx_wsasend.c create mode 100644 src/os/win32/ngx_wsasend_chain.c create mode 100644 src/stream/ngx_stream_pass_module.c diff --git a/.hgtags b/.hgtags new file mode 100644 index 0000000..0dc64a5 --- /dev/null +++ b/.hgtags @@ -0,0 +1,483 @@ +551102312e19b704cd22bd7254a9444b9ea14e96 release-0.1.0 +23fb87bddda14ce9faec90f774085634106aded4 release-0.1.1 +295d97d70c698585705345f1a8f92b02e63d6d0d release-0.1.2 +ded1284520cc939ad5ae6ddab39925375e64237d release-0.1.3 +0491b909ef7612d8411f1f59054186c1f3471b52 release-0.1.4 +a88a3e4e158fade0aaa6f3eb25597d5ced2c1075 release-0.1.5 +1f31dc6d33a3a4e65240b08066bf186df9e33b79 release-0.1.6 +5aecc125bc33d81d6214c91d73eb44230a903dde release-0.1.7 +bbd6b0b4a2b15ef8c8f1aaf7b027b6da47303524 release-0.1.8 +2ff194b74f1e60cd04670986973e3b1a6aa3bece release-0.1.9 +31ee1b50354fb829564b81a6f34e8d6ceb2d3f48 release-0.1.10 +8e8f3af115b5b903b2b8f3335de971f18891246f release-0.1.11 +c3c2848fc081e19aec5ffa97e468ad20ddb81df0 release-0.1.12 +ad1e9ebf93bb5ae4c748d471fad2de8a0afc4d2a release-0.1.13 +c5240858380136a67bec261c59b1532560b57885 release-0.1.14 +fd661d14a7fad212e326a7dad6234ea0de992fbf release-0.1.15 +621229427cba1b0af417ff2a101fc4f17a7d93c8 release-0.1.16 +4ebe09b07e3021f1a63b459903ec58f162183b26 release-0.1.17 +31ff3e943e1675a2caf745ba7a981244445d4c98 release-0.1.18 +45a460f82aec80b0f61136aa09f412436d42203a release-0.1.19 +0f836f0288eee4980f57736d50a7a60fa082d8e9 release-0.1.20 +975f62e77f0244f1b631f740be77c72c8f2da1de release-0.1.21 +fc9909c369b2b4716304ac8e38da57b8fb781211 release-0.1.22 +d7c90bb5ce83dab08715e98f9c7b81c7df4b37be release-0.1.23 +64d9afb209da0cd4a917202b7b77e51cc23e2229 release-0.1.24 +d4ea69372b946dc4ec37fc3f5ddd93ff7c3da675 release-0.1.25 +b1648294f6935e993e436fd8a68bca75c74c826d release-0.1.26 +ee66921ecd47a7fa459f70f4a9d660f91f6a1b94 release-0.1.27 +cd3117ad9aab9c58c6f7e677e551e1adbdeaba54 release-0.1.28 +9b8c906f6e63ec2c71cecebfff35819a7d32227d release-0.1.29 +c12967aadd8726daf2d85e3f3e622d89c42db176 release-0.1.30 +fbbf16224844e7d560c00043e8ade8a560415bba release-0.1.31 +417a087c9c4d9abb9b0b9b3f787aff515c43c035 release-0.1.32 +dadfa78d227027348d7f9d1e7b7093d06ba545a0 release-0.1.33 +12234c998d83bfbbaa305273b3dd1b855ca325dc release-0.1.34 +6f00349b98e5f706b82115c6e4dc84456fc0d770 release-0.1.35 +2019117e6b38cc3e89fe4f56a23b271479c627a6 release-0.1.36 +09b42134ac0c42625340f16628e29690a04f8db5 release-0.1.37 +7fa11e5c6e9612ecff5eb58274cc846ae742d1d2 release-0.1.38 +e5d7d0334fdb946133c17523c198800142ac9fe9 release-0.1.39 +c3bd8cdabb8f73e5600a91f198eb7df6fac65e92 release-0.1.40 +d6e48c08d718bf5a9e58c20a37e8ae172bff1139 release-0.1.41 +563ad09abf5042eb41e8ecaf5b4e6c9deaa42731 release-0.1.42 +c9ad0d9c7d59b2fa2a5fe669f1e88debd03e6c04 release-0.1.43 +371c1cee100d7a1b0e6cad4d188e05c98a641ee7 release-0.1.44 +b09ee85d0ac823e36861491eedfc4dfafe282997 release-0.1.45 +511a89da35ada16ae806667d699f9610b4f8499a release-0.2.0 +0148586012ab3dde69b394ec5a389d44bb11c869 release-0.2.1 +818fbd4750b99d14d2736212c939855a11b1f1ef release-0.2.2 +e16a8d574da511622b97d6237d005f40f2cddb30 release-0.2.3 +483cca23060331f2078b1c2984870d80f288ad41 release-0.2.4 +45033d85b30e3f12c407b7cfc518d76e0eda0263 release-0.2.5 +7bd37aef1e7e87858c12b124e253e98558889b50 release-0.2.6 +ecd9c160f25b7a7075dd93383d98a0fc8d8c0a41 release-0.3.0 +c1f965ef97188fd7ef81342dcf8719da18c554d2 release-0.3.1 +e48ebafc69393fc94fecfdf9997c4179fd1ce473 release-0.3.2 +9c2f3ed7a24711d3b42b124d5f831155c8beff95 release-0.3.3 +7c1369d37c7eb0017c28ebcaa0778046f5aafdcc release-0.3.4 +1af2fcb3be8a63796b6b23a488049c92a6bc12f4 release-0.3.5 +174f1e853e1e831b01000aeccfd06a9c8d4d95a2 release-0.3.6 +458b6c3fea65a894c99dd429334a77bb164c7e83 release-0.3.7 +58475592100cb792c125101b6d2d898f5adada30 release-0.3.8 +fcd6fc7ff7f9b132c35193d834e6e7d05026c716 release-0.3.9 +4d9ea73a627a914d364e83e20c58eb1283f4031d release-0.3.10 +4c5c2c55975c1152b5ca5d5d55b32d4dd7945f7a release-0.3.11 +326634fb9d47912ad94221dc2f8fa4bec424d40c release-0.3.12 +4e296b7d25bf62390ca2afb599e395426b94f785 release-0.3.13 +401de5a43ba5a8acdb9c52465193c0ea7354afe7 release-0.3.14 +284cc140593bb16ac71094acd509ab415ff4837d release-0.3.15 +d4e858a5751a7fd08e64586795ed7d336011fbc0 release-0.3.16 +8c0cdd81580eb76d774cfc5724de68e7e5cbbdc2 release-0.3.17 +425af804d968f30eeff01e33b808bc2e8c467f2c release-0.3.18 +ebc68d8ca4962fe3531b7e13444f7ac4395d9c6e release-0.3.19 +9262f520ce214d3d5fd7c842891519336ef85ca6 release-0.3.20 +869b6444d2341a587183859d4df736c7f3381169 release-0.3.21 +77f77f53214a0e3a68fef8226c15532b54f2c365 release-0.3.22 +858700ae46b453ea111b966b6d03f2c21ddcb94e release-0.3.23 +5dac8c7fb71b86aafed8ea352305e7f85759f72e release-0.3.24 +77cdfe394a94a625955e7585e09983b3af9b889b release-0.3.25 +608cf78b24ef7baaf9705e4715a361f26bb16ba9 release-0.3.26 +3f8a2132b93d66ac19bec006205a304a68524a0b release-0.3.27 +c73c5c58c619c22dd3a5a26c91bb0567a62c6930 release-0.3.28 +5ef026a2ac7481f04154f29ab49377bf99aaf96f release-0.3.29 +51b27717f140b71a2e9158807d79da17c888ce4c release-0.3.30 +7a16e281c01f1c7ab3b79c64b43ddb754ea7935e release-0.3.31 +93e85a79757c49d502e42a1cb8264a0f133b0b00 release-0.3.32 +0216fd1471f386168545f772836156761eddec08 release-0.3.33 +fbed40ce7cb4fd7203fecc22a617b9ce5b950fb3 release-0.3.34 +387450de0b4d21652f0b6242a5e26a31e3be8d8c release-0.3.35 +65bf042c0b4f39f18a235464c52f980e9fa24f6b release-0.3.36 +5d2b8078c1c2593b95ec50acfeeafbefa65be344 release-0.3.37 +f971949ffb585d400e0f15508a56232a0f897c80 release-0.3.38 +18268abd340cb351e0c01b9c44e9f8cc05492364 release-0.3.39 +e60fe4cf1d4ea3c34be8c49047c712c6d46c1727 release-0.3.40 +715d243270806d38be776fc3ed826d97514a73d6 release-0.3.41 +5e8fb59c18c19347a5607fb5af075fe1e2925b9a release-0.3.42 +947c6fd27699e0199249ad592151f844c8a900b0 release-0.3.43 +4946078f0a79e6cc952d3e410813aac9b8bda650 release-0.3.44 +95d7da23ea5315a6e9255ce036ed2c51f091f180 release-0.3.45 +1e720b0be7ecd92358da8a60944669fa493e78cd release-0.3.46 +39b7d7b33c918d8f4abc86c4075052d8c19da3c7 release-0.3.47 +7cbef16c71a1f43a07f8141f02e0135c775f0f5b release-0.3.48 +4c8cd5ae5cc100add5c08c252d991b82b1838c6b release-0.3.49 +400711951595aef7cd2ef865b84b31df52b15782 release-0.3.50 +649c9063d0fda23620eaeaf0f6393be0a672ebe7 release-0.3.51 +9079ee4735aefa98165bb2cb26dee4f58d58c1d7 release-0.3.52 +6d5c1535bb9dcd891c5963971f767421a334a728 release-0.3.53 +5fd7a5e990477189c40718c8c3e01002a2c20b81 release-0.3.54 +63a820b0bc6ca629c8e45a069b52d622ddc27a2d release-0.3.55 +562806624c4afb1687cba83bc1852f5d0fecbac3 release-0.3.56 +cec32b3753acf610ac1a6227d14032c1a89d6319 release-0.3.57 +b80f94fa2197b99db5e033fec92e0426d1fe5026 release-0.3.58 +e924670896abe2769ea0fcfd2058b405bed8e8ec release-0.3.59 +921a7ce4baf42fd1091b7e40f89c858c6b23053e release-0.3.60 +df95dcff753a6dc5e94257302aea02c18c7a7c87 release-0.3.61 +7e24168b0853ee7e46c9c7b943ef077dc64f17f5 release-0.4.0 +8183d4ba50f8500465efb27e66dd23f98775dd21 release-0.4.1 +610267a772c7bf911b499d37f66c21ce8f2ebaf7 release-0.4.2 +39dd0b045441e21512e0a6061a03d0df63414d8b release-0.4.3 +5e42c1615f4de0079bd4d8913886d588ce6a295d release-0.4.4 +40266f92b829a870808b3d4ee54c8fccdecbd2d6 release-0.4.5 +56e33c6efee7ff63cdc52bd1cf172bde195079df release-0.4.6 +119bad43bfd493400c57a05848eada2c35a46810 release-0.4.7 +0f404f82a1343cb4e4b277a44e3417385798e5e5 release-0.4.8 +d24a717314365c857b9f283d6072c2a427d5e342 release-0.4.9 +d6f0a00015fdef861fd67fb583b9690638650656 release-0.4.10 +e372368dadd7b2ecd0182b2f1b11db86fc27b2c3 release-0.4.11 +fd57967d850d2361072c72562d1ed03598473478 release-0.4.12 +979045fdcbd20cf7188545c1c589ff240251f890 release-0.4.13 +93c94cfa9f78f0a5740595dde4466ec4fba664f8 release-0.4.14 +589ee12e8d7c2ae5e4f4676bcc7a1279a76f9e8e release-0.5.0 +13416db8a807e5acb4021bc3c581203de57e2f50 release-0.5.1 +06c58edc88831fb31c492a8eddcf2c6056567f18 release-0.5.2 +e2ac5fa41bcba14adbbb722d45c083c30c07bb5c release-0.5.3 +393dbc659df15ccd411680b5c1ce87ed86d4c144 release-0.5.4 +38cc7bd8e04f2c519fd4526c12841a876be353cb release-0.5.5 +6d1fcec2ea79101c756316c015f72e75f601a5ab release-0.5.6 +aed8a9de62456c4b360358bc112ccca32ce02e8d release-0.5.7 +7642f45af67d805452df2667486201c36efaff85 release-0.5.8 +779216610662c3a459935d506f66a9b16b9c9576 release-0.5.9 +9eeb585454f3daa30cf768e95c088a092fe229b9 release-0.5.10 +bb491c8197e38ca10ae63b1f1ecb36bf6fdaf950 release-0.5.11 +613369e08810f36bbcc9734ef1059a03ccbf5e16 release-0.5.12 +bd796ef5c9c9dd34bfac20261b98685e0410122a release-0.5.13 +8a730c49f906d783b47e4b44d735efd083936c64 release-0.5.14 +cb447039152d85e9145139ff2575a6199b9af9d4 release-0.5.15 +64854c7c95d04f838585ca08492823000503fa61 release-0.5.16 +d1ffcf84ea1244f659145c36ff28de6fcdf528b2 release-0.5.17 +796a6e30ca9d29504195c10210dbc8deced0ae83 release-0.5.18 +1f81c711d2a039e1f93b9b515065a2235372d455 release-0.5.19 +8e8f6082654aedb4438c8fca408cfc316c7c5a2a release-0.5.20 +e9551132f7dd40da5719dd5bcf924c86f1436f85 release-0.5.21 +533a252896c4d1cff1586ae42129d610f7497811 release-0.5.22 +f461a49b6c747e0b67f721f2be172902afea5528 release-0.5.23 +2d5ef73671f690b65bf6d9e22e7155f68f484d5a release-0.5.24 +77bf42576050862c268e267ef3e508b145845a25 release-0.5.25 +2aefee4d4ed69eb7567680bf27a2efd212232488 release-0.6.0 +7ac0fe9bec9a2b5f8e191f6fdd6922bfd916a6cb release-0.6.1 +4882735ebc71eeec0fbfe645bdfdb31306872d82 release-0.6.2 +b94731c73d0922f472ff938b9d252ba29020f20c release-0.6.3 +13e649b813d6ccba5db33a61e08ebe09d683cd5b release-0.6.4 +80de622646b0059fd4c553eff47c391bf7503b89 release-0.6.5 +3b05edb2619d5935023b979ee7a9611b61b6c9e5 release-0.6.6 +1dcfd375100c4479611f71efb99271d0a3059215 release-0.6.7 +0228185d4c5772947b842e856ad74cf7f7fd52f3 release-0.6.8 +d1879c52326ecac45c713203670f54220879911e release-0.6.9 +5a80c6ccbe2ad24fa3d4ff6f9fe4a2b07408d19d release-0.6.10 +f88a8b0b39601b19cd740e4db614ab0b5b874686 release-0.6.11 +5557460a7247a1602ae96efd1d0ccf781344cb58 release-0.6.12 +451b02cc770a794cd41363461b446948ae1d8bc8 release-0.6.13 +537b6ef014c4a133e0ab0b7dc817508e0647e315 release-0.6.14 +5e68764f0d6e91a983170fa806e7450a9e9b33fe release-0.6.15 +158aa4e8cc46fcf9504a61469d22daf3476b17bf release-0.6.16 +d8fcca555542619228d9fab89e1665b993f8c3ee release-0.6.17 +60707ebc037086cf004736a0d4979e2a608da033 release-0.6.18 +3c2a99d3a71af846855be35e62edb9a12f363f44 release-0.6.19 +3e0a27f9358ffc1b5249e0ea2311ce7da5c8967e release-0.6.20 +143f4d65b1c875d6563ccb7f653d9157afc72194 release-0.6.21 +95e6160d2b7d0af8ffd1b95a23cadadf8f0b3f6d release-0.6.22 +69a03d5e3b6e6660079ef1ef172db7ac08d8370e release-0.6.23 +3e2a58fb48f1e1a99ebf851e0d47a7034c52ae22 release-0.6.24 +3b8607c05a8bebcfa59235c2126a70d737f0ccf5 release-0.6.25 +07ad5b2606614c4be4ee720c46cf4af126059d31 release-0.6.26 +be531addfabe5214f409d457140c1038af10d199 release-0.6.27 +58f05255d3a345d04baef5cff0ca1ae0ac7ecebb release-0.6.28 +eb2bd21dc8d03f6c94016f04ffb9adaf83a2b606 release-0.6.29 +55408deb3cd171efa9b81d23d7a1dd1ccde0b839 release-0.6.30 +d4288915bba73c4c3c9cf5d39d34e86879eb2b45 release-0.6.31 +0a189588830b8629c4dfea68feb49af36b59e4a9 release-0.7.0 +6ab27a06f3346cf9ec8737f5dbcc82dd4031e30f release-0.7.1 +a07e258cef3b0a0b6e76a6ff4ba4651c5facc85a release-0.7.2 +9992c4583513d2804fc2e7fec860fbc7ab043009 release-0.7.3 +4dc24d50230fbadfc037a414a86390db2de69dd2 release-0.7.4 +9527137b4354a648a229c7169850c7c65272c00d release-0.7.5 +c2f0f7cf306f302254beae512bda18713922375c release-0.7.6 +bbcf6d75556fdcee8bd4aba8f6c27014be9920ee release-0.7.7 +43bde71f0bbe5a33b161760d7f9f980d50386597 release-0.7.8 +769f0dd7081e9011394f264aa22aa66fd79730d8 release-0.7.9 +511edfa732da637f5f0c9476335df7dca994706d release-0.7.10 +0e7023bf6b2461309c29885935443449a41be807 release-0.7.11 +9ad1bd2b21d93902863807528e426862aedee737 release-0.7.12 +d90ea21e24ea35379aef50c5d70564158e110a15 release-0.7.13 +c07d2d20d95c83d804079bbdcecbce4a0c8282f0 release-0.7.14 +0cd7bb051f67eac2b179fb9f9cc988b9ba18ed76 release-0.7.15 +eab2e87deba73ae6abd9cc740e8d4365bed96322 release-0.7.16 +91d7a9eb8ade90e9421d7b1e3c2e47a6bc427876 release-0.7.17 +fc10f7b5cb1305fb930f8ac40b46882d0828d61e release-0.7.18 +9dba9779e37e5969a2d408c792084fd7acfec062 release-0.7.19 +61838d1bcbddc7bc4dd9f30d535573a6fddca8f9 release-0.7.20 +5f665d0fa6a5f6e748157f2ccbc445b2db8125d0 release-0.7.21 +24763afa5efe91e54f00b2ae5b87666eb6c08c3b release-0.7.22 +0562fb355a25266150cbe8c8d4e00f55e3654df3 release-0.7.23 +19c452ecd083550816873a8a31eb3ed9879085e6 release-0.7.24 +46b68faf271d6fdcaaf3ad2c69f6167ea9e9fa28 release-0.7.25 +d04bfca0c7e3ae2e4422bc1d383553139d6f0a19 release-0.7.26 +9425d9c7f8ead95b00a3929a9a5e487e0e3c8499 release-0.7.27 +fbc3e7e8b3ee756568a875f87d8a954a2f9d3bf6 release-0.7.28 +5176dfdf153fc785b18604197d58806f919829ad release-0.7.29 +87e07ccdf0a4ec53458d9d7a4ea66e1239910968 release-0.7.30 +9fddd7e1a7a27f8463867f41a461aad57df461b2 release-0.7.31 +780b2ba1ec6daf6e3773774e26b05b9ff0d5483e release-0.7.32 +83027471a25385b1c671968be761e9aa7a8591a7 release-0.7.33 +1e9a362c3dcee221ca6e34308c483ed93867aca2 release-0.7.34 +c7ee9e15717b54ead5f4a554686e74abe66c6b07 release-0.7.35 +b84548abe9b9d4f4e203f848696e52c8c82c308f release-0.7.36 +3286f0bab8e77dbc7ebb370b1dc379592ccff123 release-0.7.37 +11a4e2ed5b166b9c9f119171aa399a9e3aa4684a release-0.7.38 +f822655d4120629977794c32d3b969343b6c30db release-0.7.39 +8a350e49d2b6751296db6d8e27277ccf63ed412a release-0.7.40 +c4a56c197eeafd71fc1caef7a9d890a330e3c23d release-0.7.41 +a9575a57a5443df39611774cf3840e9088132b0e release-0.7.42 +7503d95d6eadad14c28b2db183ba09848265274b release-0.7.43 +9be652e9114435fc6f1fdec84c0458d56702db91 release-0.7.44 +797e070d480a34b31ddac0d364784773f1bbbcf9 release-0.7.45 +9b5037e7ec7db25875c40f9d1cf20a853388b124 release-0.7.46 +d1d0e6d7ff0ca3c0dd1be1ef1cfff2e3fd0b4e1c release-0.7.47 +9816fb28eda599bfd53940e6d3b6617d1ecb6323 release-0.7.48 +452b9d09df8e3f2fb04b2a33d04d2f3a6436eb34 release-0.7.49 +e4350efa7cf7a0e868c2236a1137de8a33bd8ec6 release-0.7.50 +f51f2bec766c8b6d7e1799d904f18f8ea631bd44 release-0.7.51 +18e39e566781c9c187e2eb62bebd9d669d68f08c release-0.7.52 +b073eaa1dcea296a3488b83d455fab6621a73932 release-0.7.53 +01c6fe6c2a55998434cd3b05dd10ca487ac3fb6c release-0.7.54 +3ed9377e686f2521e6ec15873084381033fb490d release-0.7.55 +a1e44954549c35023b409f728c678be8bf898148 release-0.7.56 +fbb1918a85e38a7becdb1a001dbaf5933f23a919 release-0.7.57 +87f4a49a9cc34a5b11c8784cc5ea89e97b4b2bd8 release-0.7.58 +0c22cb4862c8beb4ee1b9e4627125162a29a5304 release-0.7.59 +82d56c2425ef857cd430b8530a3f9e1127145a67 release-0.8.0 +f4acb784b53cd952559567971b97dde1e818a2b6 release-0.8.1 +b3503597c1a0f0f378afdc5e5e5b85e2c095a4be release-0.8.2 +c98da980514a02ba81c421b25bf91803ffffddf3 release-0.8.3 +db34ec0c53c4b9dec12ffdf70caf89a325ab9577 release-0.8.4 +0914802433b8678ba2cdf91280766f00f4b9b76e release-0.8.5 +ff52ee9e6422f3759f43a442b7ba615595b3a3d4 release-0.8.6 +7607237b4829fff1f60999f4663c50ed9d5182f7 release-0.8.7 +1cef1807bc12cb05ac52fb0e7a0f111d3760b569 release-0.8.8 +a40f8475511d74a468ade29c1505e8986600d7a3 release-0.8.9 +2d9faf2260df6c3e5d4aa1781493c31f27a557d0 release-0.8.10 +d0d61c32331a6505381b5218318f7b69db167ca8 release-0.8.11 +ca7a1c6c798a7eb5b294d4ac3179ec87ecf297d3 release-0.8.12 +81c8277cd8ed55febcb2dd9d9213076f6c0ccb09 release-0.8.13 +3089486a8dc5844b5b6e9f78d536b4b26f7ffa16 release-0.8.14 +d364c2c12dd9723a2dfac3f096f5e55d4cfe6838 release-0.8.15 +52163a1027c3efd6b4c461b60a2ca6266c23e193 release-0.8.16 +06564e9a2d9ec5852132c212e85eda0bf1300307 release-0.8.17 +7aaa959da85e09e29bcac3b1cadec35b0a25b64d release-0.8.18 +4bc73c644329a510da4e96b7241b80ead7772f83 release-0.8.19 +ea3d168fb99c32a5c3545717ecc61e85a375e5dd release-0.8.20 +27951ca037e63dae45ff5b6279124c224ae1255a release-0.8.21 +d56c8b5df517c2bf6e7bc2827b8bf3e08cda90e1 release-0.8.22 +3c6ac062b379b126212cbb27e98a3c8275ef381a release-0.8.23 +89b9173476de14688b1418fbf7df10f91d1719ef release-0.8.24 +aa550cb4159ae0d566006e091fb1c7a888771050 release-0.8.25 +06ce92293f6a65651b08c466f90f55bd69984b98 release-0.8.26 +ea50b0d79ef1d7d901cd0e4dcd7373447849d719 release-0.8.27 +e68b1c35cad86105ff1c5b240f53442f4c36356e release-0.8.28 +78d3582a30afe63fc0adb17c3ac8891a64e47146 release-0.8.29 +9852c5965a3292a1b6127dbb4da9fce4912d898a release-0.8.30 +4f84115914490e572bcbee5069157b7334df2744 release-0.8.31 +59dee6f7f3afeb1fad6ed5983756e48c81ad2a5c release-0.8.32 +a4456378d234c07038456cf32bfe3c651f1d5e82 release-0.8.33 +21cb50799a20575a42f9733342d37a426f79db4d release-0.8.34 +7cb3cb8d78ef7ae63561733ed91fd07933896bc8 release-0.8.35 +aed68639d4eb6afe944b7fb50499c16f7f3f503c release-0.8.36 +265b7fd2ae21c75bbffa5115b83a0123d6c4acb4 release-0.8.37 +fa5f1ca353c0c5aa5415f51d72fd7bbcc02d1ed7 release-0.8.38 +af10bf9d4c6532850aa1f70cdf7504bd109b284c release-0.8.39 +4846ec9f83cb5bc4c8519d5641b35fb9b190430c release-0.8.40 +718b4cb3faf7efe4e0648140f064bf7a92c3f7e8 release-0.8.41 +b5a3065749093282ddd19845e0b77ffc2e54333e release-0.8.42 +34df9fb22fed415cdad52def04095dc6d4b48222 release-0.8.43 +00ec8cd76fb89af27363b76c40d9f88bf4679c3b release-0.8.44 +e16dd52a0d226c23dcae9a11252564a04753bbed release-0.8.45 +f034d9173df0a433e0bbcf5974f12ea9eb9076c0 release-0.8.46 +4434dc967087315efcd0658206a67fe6c85528f3 release-0.8.47 +0b65c962e0cd6783a854877b52c903cb058eec8c release-0.8.48 +a2b7e94b9807e981866bf07e37b715847d1b7120 release-0.8.49 +e7bdb8edc1bab2bc352a9fb6ce765c46575c35bf release-0.8.50 +21dacebd12f65cb57ceb8d2688db5b07fad6e06d release-0.8.51 +67dd7533b99c8945b5b8b5b393504d4e003a1c50 release-0.8.52 +010468d890dbac33a4cae6dfb2017db70721b2fe release-0.8.53 +62b599022a2fa625b526c2ad1711dc6db7d66786 release-0.9.0 +71281dd73b17a0ead5535d531afaee098da723cb release-0.9.1 +16cff36b0e49fc9fdeee13b2e92690286bcc1b3d release-0.9.2 +b7b306325972661117694879d3e22faf4cf0df32 release-0.9.3 +fe671505a8ea86a76f0358b3ec4de84a9037ac2b release-0.9.4 +70542931bc5436d1bbd38f152245d93ac063968d release-0.9.5 +27e2f3b7a3db1819c5d0ba28327ceaba84a13c4e release-0.9.6 +657d05d63915ce2f6c4d763091059f5f85bb10e5 release-0.9.7 +e0fd9f36005923b8f98d1ba1ea583cb7625f318f release-1.0.0 +f8f89eb4e0c27e857ec517d893d4f9a454985084 release-1.0.1 +c50df367648e53d55e80b60a447c9c66caa0d326 release-1.0.2 +80d586db316512b5a9d39f00fe185f7f91523f52 release-1.0.3 +c9c2805ac9245cc48ce6efeba2b4a444f859d6aa release-1.0.4 +fa2c37b1122c2c983b6e91d1188e387d72dde4d6 release-1.0.5 +f31aea5b06654c9163be5acd6d9b7aaf0fdf6b33 release-1.1.0 +44bf95f670656fae01ccb266b3863843ea13d324 release-1.1.1 +da1289482a143dfa016769649bdff636c26f53c8 release-1.1.2 +bac8ba08a6570bac2ecd3bf2ad64b0ac3030c903 release-1.1.3 +911060bc8221d4113a693ae97952a1fa88663ca8 release-1.1.4 +e47531dfabbf8e5f8b8aff9ff353642ea4aa7abb release-1.1.5 +f9ddecfe331462f870a95e4c1c3ba1bb8f19f2d3 release-1.1.6 +378c297bb7459fb99aa9c77decac0d35391a3932 release-1.1.7 +71600ce67510af093d4bc0117a78b3b4678c6b3a release-1.1.8 +482d7d907f1ab92b78084d8b8631ed0eb7dd08f7 release-1.1.9 +c7e65deabf0db5109e8d8f6cf64cd3fb7633a3d1 release-1.1.10 +9590f0cf5aab8e6e0b0c8ae59c70187b2b97d886 release-1.1.11 +ade8fc136430cfc04a8d0885c757968b0987d56c release-1.1.12 +6a6836e65827fd3cb10a406e7bbbe36e0dad8736 release-1.1.13 +6845f4ac909233f5a08ed8a51de137713a888328 release-1.1.14 +2397e9c72f1bc5eac67006e12ad3e33e0ea9ba74 release-1.1.15 +7b7c49639a7bceecabf4963c60b26b65a77d6ce0 release-1.1.16 +f7e1113a9a1648cad122543e7080e895cf2d88f4 release-1.1.17 +2b22743c3079b41233ded0fc35af8aa89bcfab91 release-1.1.18 +0f0b425659e0b26f5bc8ea14a42dbf34de2eaba6 release-1.1.19 +f582d662cc408eb7a132c21f4b298b71d0701abb release-1.2.0 +9ee68d629722f583d43d92271f2eb84281afc630 release-1.3.0 +61b6a3438afef630774e568eefd89c53e3b93287 release-1.3.1 +7ccd50a0a455f2f2d3b241f376e1193ad956196d release-1.2.1 +0000000000000000000000000000000000000000 release-1.2.1 +50107e2d96bbfc2c59e46f889b1a5f68dd10cf19 release-1.3.2 +2c5e1e88c8cf710caf551c5c67eba00443601efe release-1.3.3 +a43447fb82aa03eabcd85352758ae14606a84d35 release-1.3.4 +90f3b4ea7992a7bf9385851a3e77173363091eea release-1.3.5 +3aeb14f88daeb973e4708310daa3dc68ac1200f7 release-1.3.6 +dafd375f1c882b15fa4a9b7aa7c801c55082395e release-1.3.7 +ab7ce0eb4cf78a656750ab1d8e55ef61f7e535ec release-1.3.8 +1b1a9337a7399ad3cdc5e3a2f9fbaaec990271d5 release-1.3.9 +2c053b2572694eb9cd4aed26a498b6cb1f51bbcc release-1.3.10 +36409ac209872ce53019f084e4e07467c5d9d25e release-1.3.11 +560dc55e90c13860a79d8f3e0d67a81c7b0257bb release-1.3.12 +dc195ffe0965b2b9072f8e213fe74ecce38f6773 release-1.3.13 +e04428778567dd4de329bbbe97ad653e22801612 release-1.3.14 +cd84e467c72967b9f5fb4d96bfc708c93edeb634 release-1.3.15 +23159600bdea695db8f9d2890aaf73424303e49c release-1.3.16 +7809529022b83157067e7d1e2fb65d57db5f4d99 release-1.4.0 +48a84bc3ff074a65a63e353b9796ff2b14239699 release-1.5.0 +99eed1a88fc33f32d66e2ec913874dfef3e12fcc release-1.5.1 +5bdca4812974011731e5719a6c398b54f14a6d61 release-1.5.2 +644a079526295aca11c52c46cb81e3754e6ad4ad release-1.5.3 +376a5e7694004048a9d073e4feb81bb54ee3ba91 release-1.5.4 +60e0409b9ec7ee194c6d8102f0656598cc4a6cfe release-1.5.5 +70c5cd3a61cb476c2afb3a61826e59c7cda0b7a7 release-1.5.6 +9ba2542d75bf62a3972278c63561fc2ef5ec573a release-1.5.7 +eaa76f24975948b0ce8be01838d949122d44ed67 release-1.5.8 +5a1759f33b7fa6270e1617c08d7e655b7b127f26 release-1.5.9 +b798fc020e3a84ef68e6c9f47865a319c826d33c release-1.5.10 +f995a10d4c7e9a817157a6ce7b753297ad32897e release-1.5.11 +97b47d95e4449cbde976657cf8cbbc118351ffe0 release-1.5.12 +fd722b890eabc600394349730a093f50dac31639 release-1.5.13 +d161d68df8be32e5cbf72b07db1a707714827803 release-1.7.0 +0351a6d89c3dbcc7a76295024ba6b70e27b9a497 release-1.7.1 +0bd223a546192fdf2e862f33938f4ec2a3b5b283 release-1.7.2 +fe7cd01828d5ca7491059f0690bb4453645eb28b release-1.7.3 +cbb146b120296852e781079d5138b04495bab6df release-1.7.4 +fe129aa02db9001d220f1db7c3c056f79482c111 release-1.7.5 +a8d111bb68847f61d682a3c8792fecb2e52efa2c release-1.7.6 +6d2fbc30f8a7f70136cf08f32d5ff3179d524873 release-1.7.7 +d5ea659b8bab2d6402a2266efa691f705e84001e release-1.7.8 +34b201c1abd1e2d4faeae4650a21574771a03c0e release-1.7.9 +860cfbcc4606ee36d898a9cd0c5ae8858db984d6 release-1.7.10 +2b3b737b5456c05cd63d3d834f4fb4d3776953d0 release-1.7.11 +3ef00a71f56420a9c3e9cec311c9a2109a015d67 release-1.7.12 +53d850fe292f157d2fb999c52788ec1dc53c91ed release-1.9.0 +884a967c369f73ab16ea859670d690fb094d3850 release-1.9.1 +3a32d6e7404a79a0973bcd8d0b83181c5bf66074 release-1.9.2 +e27a215601292872f545a733859e06d01af1017d release-1.9.3 +5cb7e2eed2031e32d2e5422caf9402758c38a6ad release-1.9.4 +942475e10cb47654205ede7ccbe7d568698e665b release-1.9.5 +b78018cfaa2f0ec20494fccb16252daa87c48a31 release-1.9.6 +54117529e40b988590ea2d38aae909b0b191663f release-1.9.7 +1bdc497c81607d854e3edf8b9a3be324c3d136b6 release-1.9.8 +ef107f3ddc237a3007e2769ec04adde0dcf627fa release-1.9.9 +be00ca08e41a69e585b6aff70a725ed6c9e1a876 release-1.9.10 +fe66cff450a95beed36a2515210eb2d7ef62c9d3 release-1.9.11 +ead3907d74f90a14d1646f1b2b56ba01d3d11702 release-1.9.12 +5936b7ed929237f1a73b467f662611cdc0309e51 release-1.9.13 +4106db71cbcb9c8274700199ac17e520902c6c0f release-1.9.14 +13070ecfda67397985f0e986eb9c42ecb46d05b5 release-1.9.15 +271ee30c6791847980cd139d31807541f5e569bf release-1.11.0 +cb783d9cc19761e14e1285d91c38f4b84d0b8756 release-1.11.1 +4d3b3a13a8cf5fc3351a7f167d1c13325e00f21c release-1.11.2 +b83a067949a3384a49fd3d943eb8d0997b31f87b release-1.11.3 +953512ca02c6f63b4fcbbc3e10d0d9835896bf99 release-1.11.4 +5253015a339aaca0a3111473d3e931b6d4752393 release-1.11.5 +5e371426b3bcba4312ce08606194b89b758927d1 release-1.11.6 +5c8f60faf33ca8926473d2da27b4c3c417bd4630 release-1.11.7 +4591da489a30f790def29bc5987f43409b503cae release-1.11.8 +20a45c768e5ed26b740679d0e22045c98727c3cc release-1.11.9 +1ad0999a7ded3d4fb01c7acf8ff57c80b643da7e release-1.11.10 +d8b321a876d6254e9e98795e3b194ef053290354 release-1.11.11 +7f394e433f0003222aa6531931ecc0b24740d5e4 release-1.11.12 +3d0e8655f897959e48cc74e87670bb5492a58871 release-1.11.13 +3671096a45bce570a2afa20b9faf42c7fb0f7e66 release-1.13.0 +539f7893ecb96bee60965528c8958d7eb2f1ce6b release-1.13.1 +5be2b25bdc65775a85f18f68a4be4f58c7384415 release-1.13.2 +8457ce87640f9bfe6221c4ac4466ced20e03bebe release-1.13.3 +bbc642c813c829963ce8197c0ca237ab7601f3d4 release-1.13.4 +0d45b4cf7c2e4e626a5a16e1fe604402ace1cea5 release-1.13.5 +f87da7d9ca02b8ced4caa6c5eb9013ccd47b0117 release-1.13.6 +47cca243d0ed39bf5dcb9859184affc958b79b6f release-1.13.7 +20ca4bcff108d3e66977f4d97508637093492287 release-1.13.8 +fb1212c7eca4c5328fe17d6cd95b010c67336aac release-1.13.9 +31c929e16910c38492581ef474e72fa67c28f124 release-1.13.10 +64179f242cb55fc206bca59de9bfdc4cf5ebcec7 release-1.13.11 +051e5fa03b92b8a564f6b12debd483d267391e82 release-1.13.12 +990b3e885636d763b97ed02d0d2cfc161a4e0c09 release-1.15.0 +4189160cb946bb38d0bc0a452b5eb4cdd8979fb5 release-1.15.1 +b234199c7ed8a156a6bb98f7ff58302c857c954f release-1.15.2 +28b3e17ca7eba1e6a0891afde0e4bc5bcc99c861 release-1.15.3 +49d49835653857daa418e68d6cbfed4958c78fca release-1.15.4 +f062e43d74fc2578bb100a9e82a953efa1eb9e4e release-1.15.5 +2351853ce6867b6166823bdf94333c0a76633c0a release-1.15.6 +051a039ce1c7e09144de4a4846669ec7116cecea release-1.15.7 +ee551e3f6dba336c0d875e266d7d55385f379b42 release-1.15.8 +d2fd76709909767fc727a5b4affcf1dc9ca488a7 release-1.15.9 +75f5c7f628411c79c7044102049f7ab4f7a246e7 release-1.15.10 +5155d0296a5ef9841f035920527ffdb771076b44 release-1.15.11 +0130ca3d58437b3c7c707cdddd813d530c68da9a release-1.15.12 +054c1c46395caff79bb4caf16f40b331f71bb6dd release-1.17.0 +7816bd7dabf6ee86c53c073b90a7143161546e06 release-1.17.1 +2fc9f853a6b7cd29dc84e0af2ed3cf78e0da6ca8 release-1.17.2 +ed4303aa1b31a9aad5440640c0840d9d0af45fed release-1.17.3 +ce2ced3856909f36f8130c99eaa4dbdbae636ddc release-1.17.4 +9af0dddbddb2c368bfedd2801bc100ffad01e19b release-1.17.5 +de68d0d94320cbf033599c6f3ca37e5335c67fd7 release-1.17.6 +e56295fe0ea76bf53b06bffa77a2d3a9a335cb8c release-1.17.7 +fdacd273711ddf20f778c1fb91529ab53979a454 release-1.17.8 +5e8d52bca714d4b85284ddb649d1ba4a3ca978a8 release-1.17.9 +c44970de01474f6f3e01b0adea85ec1d03e3a5f2 release-1.17.10 +cbe6ba650211541310618849168631ce0b788f35 release-1.19.0 +062920e2f3bf871ef7a3d8496edec1b3065faf80 release-1.19.1 +a7b46539f507e6c64efa0efda69ad60b6f4ffbce release-1.19.2 +3cbc2602325f0ac08917a4397d76f5155c34b7b1 release-1.19.3 +dc0cc425fa63a80315f6efb68697cadb6626cdf2 release-1.19.4 +8e5b068f761cd512d10c9671fbde0b568c1fd08b release-1.19.5 +f618488eb769e0ed74ef0d93cd118d2ad79ef94d release-1.19.6 +3fa6e2095a7a51acc630517e1c27a7b7ac41f7b3 release-1.19.7 +8c65d21464aaa5923775f80c32474adc7a320068 release-1.19.8 +da571b8eaf8f30f36c43b3c9b25e01e31f47149c release-1.19.9 +ffcbb9980ee2bad27b4d7b1cd680b14ff47b29aa release-1.19.10 +df34dcc9ac072ffd0945e5a1f3eb7987e8275375 release-1.21.0 +a68ac0677f8553b1f84d357bc9da114731ab5f47 release-1.21.1 +bfbc52374adcbf2f9060afd62de940f6fab3bba5 release-1.21.2 +2217a9c1d0b86026f22700b3c089545db1964f55 release-1.21.3 +39be8a682c58308d9399cddd57e37f9fdb7bdf3e release-1.21.4 +d986378168fd4d70e0121cabac274c560cca9bdf release-1.21.5 +714eb4b2c09e712fb2572a2164ce2bf67638ccac release-1.21.6 +5da2c0902e8e2aa4534008a582a60c61c135960e release-1.23.0 +a63d0a70afea96813ba6667997bc7d68b5863f0d release-1.23.1 +aa901551a7ebad1e8b0f8c11cb44e3424ba29707 release-1.23.2 +ff3afd1ce6a6b65057741df442adfaa71a0e2588 release-1.23.3 +ac779115ed6ee4f3039e9aea414a54e560450ee2 release-1.23.4 +12dcf92b0c2c68552398f19644ce3104459807d7 release-1.25.0 +f8134640e8615448205785cf00b0bc810489b495 release-1.25.1 +1d839f05409d1a50d0f15a2bf36547001f99ae40 release-1.25.2 +294a3d07234f8f65d7b0e0b0e2c5b05c12c5da0a release-1.25.3 +173a0a7dbce569adbb70257c6ec4f0f6bc585009 release-1.25.4 +8618e4d900cc71082fbe7dc72af087937d64faf5 release-1.25.5 +a58202a8c41bf0bd97eef1b946e13105a105520d release-1.26.0 +a63c124e34bcf2d1d1feb8d40ff075103b967c4c release-1.26.1 +e4c5da06073ca24e2ffc5c8f8b8d7833a926356f release-1.26.2 diff --git a/CHANGES b/CHANGES index c356ba7..9c2fb00 100644 --- a/CHANGES +++ b/CHANGES @@ -1,15 +1,310 @@ -Changes with nginx 1.22.1 19 Oct 2022 +Changes with nginx 1.26.3 05 Feb 2025 + + *) Security: insufficient check in virtual servers handling with TLSv1.3 + SNI allowed to reuse SSL sessions in a different virtual server, to + bypass client SSL certificates verification (CVE-2025-23419). + + *) Bugfix: in the ngx_http_mp4_module. + Thanks to Nils Bars. + + *) Workaround: "gzip filter failed to use preallocated memory" alerts + appeared in logs when using zlib-ng. + + *) Bugfix: nginx could not build libatomic library using the library + sources if the --with-libatomic=DIR option was used. + + *) Bugfix: nginx now ignores QUIC version negotiation packets from + clients. + + *) Bugfix: nginx could not be built on Solaris 10 and earlier with the + ngx_http_v3_module. + + *) Bugfixes in HTTP/3. + + +Changes with nginx 1.26.2 14 Aug 2024 + + *) Security: processing of a specially crafted mp4 file by the + ngx_http_mp4_module might cause a worker process crash + (CVE-2024-7347). + Thanks to Nils Bars. + + +Changes with nginx 1.26.1 29 May 2024 + + *) Security: when using HTTP/3, processing of a specially crafted QUIC + session might cause a worker process crash, worker process memory + disclosure on systems with MTU larger than 4096 bytes, or might have + potential other impact (CVE-2024-32760, CVE-2024-31079, + CVE-2024-35200, CVE-2024-34161). + Thanks to Nils Bars of CISPA. + + *) Bugfix: reduced memory consumption for long-lived requests if "gzip", + "gunzip", "ssi", "sub_filter", or "grpc_pass" directives are used. + + *) Bugfix: nginx could not be built by gcc 14 if the --with-libatomic + option was used. + Thanks to Edgar Bonet. + + *) Bugfix: in HTTP/3. + + +Changes with nginx 1.26.0 23 Apr 2024 + + *) 1.26.x stable branch. + + +Changes with nginx 1.25.5 16 Apr 2024 + + *) Feature: virtual servers in the stream module. + + *) Feature: the ngx_stream_pass_module. + + *) Feature: the "deferred", "accept_filter", and "setfib" parameters of + the "listen" directive in the stream module. + + *) Feature: cache line size detection for some architectures. + Thanks to Piotr Sikora. + + *) Feature: support for Homebrew on Apple Silicon. + Thanks to Piotr Sikora. + + *) Bugfix: Windows cross-compilation bugfixes and improvements. + Thanks to Piotr Sikora. + + *) Bugfix: unexpected connection closure while using 0-RTT in QUIC. + Thanks to Vladimir Khomutov. + + +Changes with nginx 1.25.4 14 Feb 2024 + + *) Security: when using HTTP/3 a segmentation fault might occur in a + worker process while processing a specially crafted QUIC session + (CVE-2024-24989, CVE-2024-24990). + + *) Bugfix: connections with pending AIO operations might be closed + prematurely during graceful shutdown of old worker processes. + + *) Bugfix: socket leak alerts no longer logged when fast shutdown was + requested after graceful shutdown of old worker processes. + + *) Bugfix: a socket descriptor error, a socket leak, or a segmentation + fault in a worker process (for SSL proxying) might occur if AIO was + used in a subrequest. + + *) Bugfix: a segmentation fault might occur in a worker process if SSL + proxying was used along with the "image_filter" directive and errors + with code 415 were redirected with the "error_page" directive. + + *) Bugfixes and improvements in HTTP/3. + + +Changes with nginx 1.25.3 24 Oct 2023 + + *) Change: improved detection of misbehaving clients when using HTTP/2. + + *) Feature: startup speedup when using a large number of locations. + Thanks to Yusuke Nojima. + + *) Bugfix: a segmentation fault might occur in a worker process when + using HTTP/2 without SSL; the bug had appeared in 1.25.1. + + *) Bugfix: the "Status" backend response header line with an empty + reason phrase was handled incorrectly. + + *) Bugfix: memory leak during reconfiguration when using the PCRE2 + library. + Thanks to ZhenZhong Wu. + + *) Bugfixes and improvements in HTTP/3. + + +Changes with nginx 1.25.2 15 Aug 2023 + + *) Feature: path MTU discovery when using HTTP/3. + + *) Feature: TLS_AES_128_CCM_SHA256 cipher suite support when using + HTTP/3. + + *) Change: now nginx uses appname "nginx" when loading OpenSSL + configuration. + + *) Change: now nginx does not try to load OpenSSL configuration if the + --with-openssl option was used to built OpenSSL and the OPENSSL_CONF + environment variable is not set. + + *) Bugfix: in the $body_bytes_sent variable when using HTTP/3. + + *) Bugfix: in HTTP/3. + + +Changes with nginx 1.25.1 13 Jun 2023 + + *) Feature: the "http2" directive, which enables HTTP/2 on a per-server + basis; the "http2" parameter of the "listen" directive is now + deprecated. + + *) Change: HTTP/2 server push support has been removed. + + *) Change: the deprecated "ssl" directive is not supported anymore. + + *) Bugfix: in HTTP/3 when using OpenSSL. + + +Changes with nginx 1.25.0 23 May 2023 + + *) Feature: experimental HTTP/3 support. + + +Changes with nginx 1.23.4 28 Mar 2023 + + *) Change: now TLSv1.3 protocol is enabled by default. + + *) Change: now nginx issues a warning if protocol parameters of a + listening socket are redefined. + + *) Change: now nginx closes connections with lingering if pipelining was + used by the client. + + *) Feature: byte ranges support in the ngx_http_gzip_static_module. + + *) Bugfix: port ranges in the "listen" directive did not work; the bug + had appeared in 1.23.3. + Thanks to Valentin Bartenev. + + *) Bugfix: incorrect location might be chosen to process a request if a + prefix location longer than 255 characters was used in the + configuration. + + *) Bugfix: non-ASCII characters in file names on Windows were not + supported by the ngx_http_autoindex_module, the ngx_http_dav_module, + and the "include" directive. + + *) Change: the logging level of the "data length too long", "length too + short", "bad legacy version", "no shared signature algorithms", "bad + digest length", "missing sigalgs extension", "encrypted length too + long", "bad length", "bad key update", "mixed handshake and non + handshake data", "ccs received early", "data between ccs and + finished", "packet length too long", "too many warn alerts", "record + too small", and "got a fin before a ccs" SSL errors has been lowered + from "crit" to "info". + + *) Bugfix: a socket leak might occur when using HTTP/2 and the + "error_page" directive to redirect errors with code 400. + + *) Bugfix: messages about logging to syslog errors did not contain + information that the errors happened while logging to syslog. + Thanks to Safar Safarly. + + *) Workaround: "gzip filter failed to use preallocated memory" alerts + appeared in logs when using zlib-ng. + + *) Bugfix: in the mail proxy server. + + +Changes with nginx 1.23.3 13 Dec 2022 + + *) Bugfix: an error might occur when reading PROXY protocol version 2 + header with large number of TLVs. + + *) Bugfix: a segmentation fault might occur in a worker process if SSI + was used to process subrequests created by other modules. + Thanks to Ciel Zhao. + + *) Workaround: when a hostname used in the "listen" directive resolves + to multiple addresses, nginx now ignores duplicates within these + addresses. + + *) Bugfix: nginx might hog CPU during unbuffered proxying if SSL + connections to backends were used. + + +Changes with nginx 1.23.2 19 Oct 2022 *) Security: processing of a specially crafted mp4 file by the ngx_http_mp4_module might cause a worker process crash, worker process memory disclosure, or might have potential other impact (CVE-2022-41741, CVE-2022-41742). + *) Feature: the "$proxy_protocol_tlv_..." variables. + + *) Feature: TLS session tickets encryption keys are now automatically + rotated when using shared memory in the "ssl_session_cache" + directive. + + *) Change: the logging level of the "bad record type" SSL errors has + been lowered from "crit" to "info". + Thanks to Murilo Andrade. + + *) Change: now when using shared memory in the "ssl_session_cache" + directive the "could not allocate new session" errors are logged at + the "warn" level instead of "alert" and not more often than once per + second. + + *) Bugfix: nginx/Windows could not be built with OpenSSL 3.0.x. + + *) Bugfix: in logging of the PROXY protocol errors. + Thanks to Sergey Brester. + + *) Workaround: shared memory from the "ssl_session_cache" directive was + spent on sessions using TLS session tickets when using TLSv1.3 with + OpenSSL. + + *) Workaround: timeout specified with the "ssl_session_timeout" + directive did not work when using TLSv1.3 with OpenSSL or BoringSSL. + + +Changes with nginx 1.23.1 19 Jul 2022 + + *) Feature: memory usage optimization in configurations with SSL + proxying. + + *) Feature: looking up of IPv4 addresses while resolving now can be + disabled with the "ipv4=off" parameter of the "resolver" directive. + + *) Change: the logging level of the "bad key share", "bad extension", + "bad cipher", and "bad ecpoint" SSL errors has been lowered from + "crit" to "info". + + *) Bugfix: while returning byte ranges nginx did not remove the + "Content-Range" header line if it was present in the original backend + response. + + *) Bugfix: a proxied response might be truncated during reconfiguration + on Linux; the bug had appeared in 1.17.5. + + +Changes with nginx 1.23.0 21 Jun 2022 + + *) Change in internal API: now header lines are represented as linked + lists. + + *) Change: now nginx combines arbitrary header lines with identical + names when sending to FastCGI, SCGI, and uwsgi backends, in the + $r->header_in() method of the ngx_http_perl_module, and during lookup + of the "$http_...", "$sent_http_...", "$sent_trailer_...", + "$upstream_http_...", and "$upstream_trailer_..." variables. + + *) Bugfix: if there were multiple "Vary" header lines in the backend + response, nginx only used the last of them when caching. + + *) Bugfix: if there were multiple "WWW-Authenticate" header lines in the + backend response and errors with code 401 were intercepted or the + "auth_request" directive was used, nginx only sent the first of the + header lines to the client. + + *) Change: the logging level of the "application data after close + notify" SSL errors has been lowered from "crit" to "info". -Changes with nginx 1.22.0 24 May 2022 + *) Bugfix: connections might hang if nginx was built on Linux 2.6.17 or + newer, but was used on systems without EPOLLRDHUP support, notably + with epoll emulation layers; the bug had appeared in 1.17.5. + Thanks to Marcus Ball. - *) 1.22.x stable branch. + *) Bugfix: nginx did not cache the response if the "Expires" response + header line disabled caching, but following "Cache-Control" header + line enabled caching. Changes with nginx 1.21.6 25 Jan 2022 diff --git a/CHANGES.ru b/CHANGES.ru index 9ee9e11..0d1ba20 100644 --- a/CHANGES.ru +++ b/CHANGES.ru @@ -1,5 +1,235 @@ -Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.22.1 19.10.2022 +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.26.3 05.02.2025 + + *) БезопаÑноÑть: недоÑÑ‚Ð°Ñ‚Ð¾Ñ‡Ð½Ð°Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ° в обработке виртуальных Ñерверов + при иÑпользовании SNI в TLSv1.3 позволÑла повторно иÑпользовать + SSL-ÑеÑÑию в контекÑте другого виртуального Ñервера, чтобы обойти + проверку клиентÑких SSL-Ñертификатов (CVE-2025-23419). + + *) ИÑправление: в модуле ngx_http_mp4_module. + СпаÑибо Nils Bars. + + *) Изменение: при иÑпользовании zlib-ng в логах поÑвлÑлиÑÑŒ ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + "gzip filter failed to use preallocated memory". + + *) ИÑправление: nginx не мог Ñобрать библиотеку libatomic из иÑходных + текÑтов, еÑли иÑпользовалÑÑ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€ --with-libatomic=DIR. + + *) ИÑправление: теперь nginx игнорирует пакеты ÑоглаÑÐ¾Ð²Ð°Ð½Ð¸Ñ Ð²ÐµÑ€Ñий QUIC + от клиентов. + + *) ИÑправление: nginx не ÑобиралÑÑ Ð½Ð° Solaris 10 и более ранних Ñ + модулем ngx_http_v3_module. + + *) ИÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð² HTTP/3. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.26.2 14.08.2024 + + *) БезопаÑноÑть: обработка Ñпециально Ñозданного mp4-файла модулем + ngx_http_mp4_module могла приводить к падению рабочего процеÑÑа + (CVE-2024-7347). + СпаÑибо Nils Bars. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.26.1 29.05.2024 + + *) БезопаÑноÑть: при иÑпользовании HTTP/3 обработка Ñпециально Ñозданной + QUIC-ÑеÑÑии могла приводить к падению рабочего процеÑÑа, отправке + клиенту Ñодержимого памÑти рабочего процеÑÑа на ÑиÑтемах Ñ MTU больше + 4096 байт, а также потенциально могла иметь другие поÑледÑÑ‚Ð²Ð¸Ñ + (CVE-2024-32760, CVE-2024-31079, CVE-2024-35200, CVE-2024-34161). + СпаÑибо Nils Bars из CISPA. + + *) ИÑправление: уменьшено потребление памÑти Ð´Ð»Ñ Ð´Ð¾Ð»Ð³Ð¾Ð¶Ð¸Ð²ÑƒÑ‰Ð¸Ñ… запроÑов, + еÑли иÑпользуютÑÑ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¸Ð²Ñ‹ gzip, gunzip, ssi, sub_filter или + grpc_pass. + + *) ИÑправление: nginx не ÑобиралÑÑ gcc 14, еÑли иÑпользовалÑÑ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€ + --with-libatomic. + СпаÑибо Edgar Bonet. + + *) ИÑправление: в HTTP/3. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.26.0 23.04.2024 + + *) Ð¡Ñ‚Ð°Ð±Ð¸Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ‚ÐºÐ° 1.26.x. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.25.5 16.04.2024 + + *) Добавление: виртуальные Ñервера в модуле stream. + + *) Добавление: модуль ngx_stream_pass_module. + + *) Добавление: параметры deferred, accept_filter и setfib директивы + listen в модуле stream. + + *) Добавление: определение размера Ñтроки кеша процеÑÑора Ð´Ð»Ñ Ð½ÐµÐºÐ¾Ñ‚Ð¾Ñ€Ñ‹Ñ… + архитектур. + СпаÑибо Piotr Sikora. + + *) Добавление: поддержка Homebrew на Apple Silicon. + СпаÑибо Piotr Sikora. + + *) ИÑправление: ÑƒÐ»ÑƒÑ‡ÑˆÐµÐ½Ð¸Ñ Ð¸ иÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ ÐºÑ€Ð¾ÑÑ-компилÑции Ð´Ð»Ñ Windows. + СпаÑибо Piotr Sikora. + + *) ИÑправление: неожиданное закрытие ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¿Ñ€Ð¸ иÑпользовании 0-RTT + в QUIC. + СпаÑибо Владимиру Хомутову. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.25.4 14.02.2024 + + *) БезопаÑноÑть: при иÑпользовании HTTP/3 в рабочем процеÑÑе мог + произойти segmentation fault во Ð²Ñ€ÐµÐ¼Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ Ñпециально Ñозданной + QUIC-ÑеÑÑии (CVE-2024-24989, CVE-2024-24990). + + *) ИÑправление: ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ Ð½ÐµÐ·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð½Ñ‹Ð¼Ð¸ AIO-операциÑми могли + закрыватьÑÑ Ð¿Ñ€ÐµÐ¶Ð´ÐµÐ²Ñ€ÐµÐ¼ÐµÐ½Ð½Ð¾ во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð»Ð°Ð²Ð½Ð¾Ð³Ð¾ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñтарых + рабочих процеÑÑов. + + *) ИÑправление: теперь nginx не пишет в лог ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ð± утечке Ñокетов, + еÑли во Ð²Ñ€ÐµÐ¼Ñ Ð¿Ð»Ð°Ð²Ð½Ð¾Ð³Ð¾ Ð·Ð°Ð²ÐµÑ€ÑˆÐµÐ½Ð¸Ñ Ñтарых рабочих процеÑÑов было + запрошено быÑтрое завершение. + + *) ИÑправление: при иÑпользовании AIO в подзапроÑе могла проиÑходить + ошибка на Ñокете, утечка Ñокетов, либо segmentation fault в рабочем + процеÑÑе (при SSL-прокÑировании). + + *) ИÑправление: в рабочем процеÑÑе мог произойти segmentation fault, + еÑли иÑпользовалоÑÑŒ SSL-прокÑирование и директива image_filter, а + ошибки Ñ ÐºÐ¾Ð´Ð¾Ð¼ 415 перенаправлÑлиÑÑŒ Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ директивы error_page. + + *) ИÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¸ ÑƒÐ»ÑƒÑ‡ÑˆÐµÐ½Ð¸Ñ Ð² HTTP/3. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.25.3 24.10.2023 + + *) Изменение: улучшено детектирование некорректного Ð¿Ð¾Ð²ÐµÐ´ÐµÐ½Ð¸Ñ ÐºÐ»Ð¸ÐµÐ½Ñ‚Ð¾Ð² + при иÑпользовании HTTP/2. + + *) Добавление: уменьшение времени запуÑка при иÑпользовании большого + количеÑтва location'ов. + СпаÑибо Yusuke Nojima. + + *) ИÑправление: при иÑпользовании HTTP/2 без SSL в рабочем процеÑÑе мог + произойти segmentation fault; ошибка поÑвилаÑÑŒ в 1.25.1. + + *) ИÑправление: Ñтрока "Status" в заголовке ответа бÑкенда Ñ Ð¿ÑƒÑтой + поÑÑнÑющей фразой обрабатывалаÑÑŒ некорректно. + + *) ИÑправление: утечки памÑти во Ð²Ñ€ÐµÐ¼Ñ Ð¿ÐµÑ€ÐµÐºÐ¾Ð½Ñ„Ð¸Ð³ÑƒÑ€Ð°Ñ†Ð¸Ð¸ при + иÑпользовании библиотеки PCRE2. + СпаÑибо ZhenZhong Wu. + + *) ИÑÐ¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¸ ÑƒÐ»ÑƒÑ‡ÑˆÐµÐ½Ð¸Ñ Ð² HTTP/3. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.25.2 15.08.2023 + + *) Добавление: path MTU discovery при иÑпользовании HTTP/3. + + *) Добавление: поддержка шифра TLS_AES_128_CCM_SHA256 при иÑпользовании + HTTP/3. + + *) Изменение: теперь при загрузке конфигурации OpenSSL nginx иÑпользует + appname "nginx". + + *) Изменение: теперь nginx не пытаетÑÑ Ð·Ð°Ð³Ñ€ÑƒÐ¶Ð°Ñ‚ÑŒ конфигурацию OpenSSL, + еÑли Ð´Ð»Ñ Ñборки OpenSSL иÑпользовалÑÑ Ð¿Ð°Ñ€Ð°Ð¼ÐµÑ‚Ñ€ --with-openssl и + Ð¿ÐµÑ€ÐµÐ¼ÐµÐ½Ð½Ð°Ñ Ð¾ÐºÑ€ÑƒÐ¶ÐµÐ½Ð¸Ñ OPENSSL_CONF не уÑтановлена. + + *) ИÑправление: в переменной $body_bytes_sent при иÑпользовании HTTP/3. + + *) ИÑправление: в HTTP/3. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.25.1 13.06.2023 + + *) Добавление: директива http2, позволÑÑŽÑ‰Ð°Ñ Ð²ÐºÐ»ÑŽÑ‡Ð°Ñ‚ÑŒ HTTP/2 в отдельных + блоках server; параметр http2 директивы listen объÑвлен уÑтаревшим. + + *) Изменение: поддержка HTTP/2 server push упразднена. + + *) Изменение: уÑÑ‚Ð°Ñ€ÐµÐ²ÑˆÐ°Ñ Ð´Ð¸Ñ€ÐµÐºÑ‚Ð¸Ð²Ð° ssl больше не поддерживаетÑÑ. + + *) ИÑправление: в HTTP/3 при иÑпользовании OpenSSL. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.25.0 23.05.2023 + + *) Добавление: ÑкÑÐ¿ÐµÑ€Ð¸Ð¼ÐµÐ½Ñ‚Ð°Ð»ÑŒÐ½Ð°Ñ Ð¿Ð¾Ð´Ð´ÐµÑ€Ð¶ÐºÐ° HTTP/3. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.23.4 28.03.2023 + + *) Изменение: теперь протокол TLSv1.3 разрешён по умолчанию. + + *) Изменение: теперь nginx выдаёт предупреждение при переопределении + параметров listen-Ñокета, задающих иÑпользуемые протоколы. + + *) Изменение: теперь, еÑли клиент иÑпользует pipelining, nginx закрывает + ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ Ð¾Ð¶Ð¸Ð´Ð°Ð½Ð¸ÐµÐ¼ дополнительных данных (lingering close). + + *) Добавление: поддержка byte ranges Ð´Ð»Ñ Ð¾Ñ‚Ð²ÐµÑ‚Ð¾Ð² Ð¼Ð¾Ð´ÑƒÐ»Ñ + ngx_http_gzip_static_module. + + *) ИÑправление: диапазоны портов в директиве listen не работали; ошибка + поÑвилаÑÑŒ в 1.23.3. + СпаÑибо Валентину Бартеневу. + + *) ИÑправление: Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ запроÑа мог быть выбран неверный location, + еÑли в конфигурации иÑпользовалÑÑ Ð¿Ñ€ÐµÑ„Ð¸ÐºÑный location длиннее 255 + Ñимволов. + + *) ИÑправление: не-ASCII Ñимволы в именах файлов на Windows не + поддерживалиÑÑŒ модулÑми ngx_http_autoindex_module и + ngx_http_dav_module, а также директивой include. + + *) Изменение: уровень Ð»Ð¾Ð³Ð³Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾ÑˆÐ¸Ð±Ð¾Ðº SSL "data length too long", + "length too short", "bad legacy version", "no shared signature + algorithms", "bad digest length", "missing sigalgs extension", + "encrypted length too long", "bad length", "bad key update", "mixed + handshake and non handshake data", "ccs received early", "data + between ccs and finished", "packet length too long", "too many warn + alerts", "record too small", и "got a fin before a ccs" понижен Ñ + ÑƒÑ€Ð¾Ð²Ð½Ñ crit до info. + + *) ИÑправление: при иÑпользовании HTTP/2 и директивы error_page Ð´Ð»Ñ + Ð¿ÐµÑ€ÐµÐ½Ð°Ð¿Ñ€Ð°Ð²Ð»ÐµÐ½Ð¸Ñ Ð¾ÑˆÐ¸Ð±Ð¾Ðº Ñ ÐºÐ¾Ð´Ð¾Ð¼ 400 могла проиÑходить утечка Ñокетов. + + *) ИÑправление: ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ Ð¾Ð± ошибках запиÑи в syslog не Ñодержали + информации о том, что ошибки проиÑходили в процеÑÑе запиÑи в syslog. + СпаÑибо Safar Safarly. + + *) Изменение: при иÑпользовании zlib-ng в логах поÑвлÑлиÑÑŒ ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ + "gzip filter failed to use preallocated memory". + + *) ИÑправление: в почтовом прокÑи-Ñервере. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.23.3 13.12.2022 + + *) ИÑправление: при чтении заголовка протокола PROXY верÑии 2, + Ñодержащего большое количеÑтво TLV, могла возникать ошибка. + + *) ИÑправление: при иÑпользовании SSI Ð´Ð»Ñ Ð¾Ð±Ñ€Ð°Ð±Ð¾Ñ‚ÐºÐ¸ подзапроÑов, + Ñозданных другими модулÑми, в рабочем процеÑÑе мог произойти + segmentation fault. + СпаÑибо Ciel Zhao. + + *) Изменение: теперь, еÑли при преобразовании в адреÑа имени хоÑта, + указанного в директиве listen, возвращаетÑÑ Ð½ÐµÑколько адреÑов, nginx + игнорирует дубликаты Ñреди Ñтих адреÑов. + + *) ИÑправление: nginx мог нагружать процеÑÑор при небуферизированном + прокÑировании, еÑли иÑпользовалиÑÑŒ SSL-ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ñ Ð±Ñкендами. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.23.2 19.10.2022 *) БезопаÑноÑть: обработка Ñпециально Ñозданного mp4-файла модулем ngx_http_mp4_module могла приводить к падению рабочего процеÑÑа, @@ -7,10 +237,84 @@ потенциально могла иметь другие поÑледÑÑ‚Ð²Ð¸Ñ (CVE-2022-41741, CVE-2022-41742). + *) Добавление: переменные "$proxy_protocol_tlv_...". + + *) Добавление: ключи ÑˆÐ¸Ñ„Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ TLS session tickets теперь автоматичеÑки + менÑÑŽÑ‚ÑÑ Ð¿Ñ€Ð¸ иÑпользовании разделÑемой памÑти в ssl_session_cache. + + *) Изменение: уровень Ð»Ð¾Ð³Ð³Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾ÑˆÐ¸Ð±Ð¾Ðº SSL "bad record type" понижен + Ñ ÑƒÑ€Ð¾Ð²Ð½Ñ crit до info. + СпаÑибо Murilo Andrade. + + *) Изменение: теперь при иÑпользовании разделÑемой памÑти в + ssl_session_cache ÑÐ¾Ð¾Ð±Ñ‰ÐµÐ½Ð¸Ñ "could not allocate new session" + логгируютÑÑ Ð½Ð° уровне warn вмеÑто alert и не чаще одного раза в + Ñекунду. + + *) ИÑправление: nginx/Windows не ÑобиралÑÑ Ñ OpenSSL 3.0.x. + + *) ИÑправление: в логгировании ошибок протокола PROXY. + СпаÑибо Сергею БреÑтеру. + + *) Изменение: при иÑпользовании TLSv1.3 Ñ OpenSSL разделÑÐµÐ¼Ð°Ñ Ð¿Ð°Ð¼Ñть из + ssl_session_cache раÑходовалаÑÑŒ в том чиÑле на ÑеÑÑии, иÑпользующие + TLS session tickets. + + *) Изменение: таймаут, заданный Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ директивы ssl_session_timeout, + не работал при иÑпользовании TLSv1.3 Ñ OpenSSL или BoringSSL. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.23.1 19.07.2022 + + *) Добавление: Ð¾Ð¿Ñ‚Ð¸Ð¼Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð¸ÑÐ¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¿Ð°Ð¼Ñти в конфигурациÑÑ… Ñ + SSL-прокÑированием. + + *) Добавление: теперь Ñ Ð¿Ð¾Ð¼Ð¾Ñ‰ÑŒÑŽ параметра "ipv4=off" директивы + "resolver" можно запретить поиÑк IPv4-адреÑов при преобразовании имён + в адреÑа. + + *) Изменение: уровень Ð»Ð¾Ð³Ð³Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾ÑˆÐ¸Ð±Ð¾Ðº SSL "bad key share", "bad + extension", "bad cipher" и "bad ecpoint" понижен Ñ ÑƒÑ€Ð¾Ð²Ð½Ñ crit до + info. + + *) ИÑправление: при возврате диапазонов nginx не удалÑл Ñтроку заголовка + "Content-Range", еÑли она приÑутÑтвовала в иÑходном ответе бÑкенда. + + *) ИÑправление: прокÑированный ответ мог быть отправлен не полноÑтью при + переконфигурации на Linux; ошибка поÑвилаÑÑŒ в 1.17.5. + + +Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.23.0 21.06.2022 + + *) Изменение во внутреннем API: теперь Ñтроки заголовков предÑтавлены + ÑвÑзными ÑпиÑками. + + *) Изменение: теперь nginx объединÑет произвольные Ñтроки заголовков Ñ + одинаковыми именами при отправке на FastCGI-, SCGI- и uwsgi-бÑкенды, + в методе $r->header_in() Ð¼Ð¾Ð´ÑƒÐ»Ñ ngx_http_perl_module, и при доÑтупе + через переменные "$http_...", "$sent_http_...", "$sent_trailer_...", + "$upstream_http_..." и "$upstream_trailer_...". + + *) ИÑправление: еÑли в заголовке ответа бÑкенда было неÑколько Ñтрок + "Vary", при кÑшировании nginx учитывал только поÑледнюю из них. + + *) ИÑправление: еÑли в заголовке ответа бÑкенда было неÑколько Ñтрок + "WWW-Authenticate" и иÑпользовалÑÑ Ð¿ÐµÑ€ÐµÑ…Ð²Ð°Ñ‚ ошибок Ñ ÐºÐ¾Ð´Ð¾Ð¼ 401 от + бÑкенда или директива auth_request, nginx переÑылал клиенту только + первую из Ñтих Ñтрок. + + *) Изменение: уровень Ð»Ð¾Ð³Ð³Ð¸Ñ€Ð¾Ð²Ð°Ð½Ð¸Ñ Ð¾ÑˆÐ¸Ð±Ð¾Ðº SSL "application data after + close notify" понижен Ñ ÑƒÑ€Ð¾Ð²Ð½Ñ crit до info. -Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.22.0 24.05.2022 + *) ИÑправление: ÑÐ¾ÐµÐ´Ð¸Ð½ÐµÐ½Ð¸Ñ Ð¼Ð¾Ð³Ð»Ð¸ завиÑать, еÑли nginx был Ñобран на + Linux 2.6.17 и новее, а иÑпользовалÑÑ Ð½Ð° ÑиÑтемах без поддержки + EPOLLRDHUP, в чаÑтноÑти, на ÑиÑтемах Ñ ÑмулÑцией epoll; ошибка + поÑвилаÑÑŒ в 1.17.5. + СпаÑибо Marcus Ball. - *) Ð¡Ñ‚Ð°Ð±Ð¸Ð»ÑŒÐ½Ð°Ñ Ð²ÐµÑ‚ÐºÐ° 1.22.x. + *) ИÑправление: nginx не кÑшировал ответ, еÑли Ñтрока заголовка ответа + "Expires" запрещала кÑширование, а поÑÐ»ÐµÐ´ÑƒÑŽÑ‰Ð°Ñ Ñтрока заголовка + "Cache-Control" разрешала кÑширование. Ð˜Ð·Ð¼ÐµÐ½ÐµÐ½Ð¸Ñ Ð² nginx 1.21.6 25.01.2022 diff --git a/LICENSE b/LICENSE index fdedcb7..985470e 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ /* * Copyright (C) 2002-2021 Igor Sysoev - * Copyright (C) 2011-2022 Nginx, Inc. + * Copyright (C) 2011-2024 Nginx, Inc. * All rights reserved. * * Redistribution and use in source and binary forms, with or without diff --git a/auto/cc/conf b/auto/cc/conf index afbca62..ba31cb8 100644 --- a/auto/cc/conf +++ b/auto/cc/conf @@ -117,7 +117,7 @@ else . auto/cc/acc ;; - msvc*) + msvc) # MSVC++ 6.0 SP2, MSVC++ Toolkit 2003 . auto/cc/msvc diff --git a/auto/cc/msvc b/auto/cc/msvc index 68435ff..567bac7 100644 --- a/auto/cc/msvc +++ b/auto/cc/msvc @@ -11,8 +11,8 @@ # MSVC 2015 (14.0) cl 19.00 -NGX_MSVC_VER=`$NGX_WINE $CC 2>&1 | grep 'Compiler Version' 2>&1 \ - | sed -e 's/^.* Version \(.*\)/\1/'` +NGX_MSVC_VER=`$NGX_WINE $CC 2>&1 | grep 'C/C++.* [0-9][0-9]*\.[0-9]' 2>&1 \ + | sed -e 's/^.* \([0-9][0-9]*\.[0-9].*\)/\1/'` echo " + cl version: $NGX_MSVC_VER" @@ -22,6 +22,21 @@ have=NGX_COMPILER value="\"cl $NGX_MSVC_VER\"" . auto/define ngx_msvc_ver=`echo $NGX_MSVC_VER | sed -e 's/^\([0-9]*\).*/\1/'` +# detect x64 builds + +case "$NGX_MSVC_VER" in + + *x64) + NGX_MACHINE=amd64 + ;; + + *) + NGX_MACHINE=i386 + ;; + +esac + + # optimizations # maximize speed, equivalent to -Og -Oi -Ot -Oy -Ob2 -Gs -GF -Gy diff --git a/auto/install b/auto/install index c764fdd..7f73e4b 100644 --- a/auto/install +++ b/auto/install @@ -112,7 +112,7 @@ install: build $NGX_INSTALL_PERL_MODULES test ! -f '\$(DESTDIR)$NGX_SBIN_PATH' \\ || mv '\$(DESTDIR)$NGX_SBIN_PATH' \\ '\$(DESTDIR)$NGX_SBIN_PATH.old' - cp $NGX_OBJS/nginx '\$(DESTDIR)$NGX_SBIN_PATH' + cp $NGX_OBJS/nginx$ngx_binext '\$(DESTDIR)$NGX_SBIN_PATH' test -d '\$(DESTDIR)$NGX_CONF_PREFIX' \\ || mkdir -p '\$(DESTDIR)$NGX_CONF_PREFIX' diff --git a/auto/lib/geoip/conf b/auto/lib/geoip/conf index 8302aae..47165b1 100644 --- a/auto/lib/geoip/conf +++ b/auto/lib/geoip/conf @@ -64,6 +64,23 @@ if [ $ngx_found = no ]; then fi +if [ $ngx_found = no ]; then + + # Homebrew on Apple Silicon + + ngx_feature="GeoIP library in /opt/homebrew/" + ngx_feature_path="/opt/homebrew/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/homebrew/lib -L/opt/homebrew/lib -lGeoIP" + else + ngx_feature_libs="-L/opt/homebrew/lib -lGeoIP" + fi + + . auto/feature +fi + + if [ $ngx_found = yes ]; then CORE_INCS="$CORE_INCS $ngx_feature_path" diff --git a/auto/lib/google-perftools/conf b/auto/lib/google-perftools/conf index 7f1a911..94dadac 100644 --- a/auto/lib/google-perftools/conf +++ b/auto/lib/google-perftools/conf @@ -46,6 +46,22 @@ if [ $ngx_found = no ]; then fi +if [ $ngx_found = no ]; then + + # Homebrew on Apple Silicon + + ngx_feature="Google perftools in /opt/homebrew/" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/homebrew/lib -L/opt/homebrew/lib -lprofiler" + else + ngx_feature_libs="-L/opt/homebrew/lib -lprofiler" + fi + + . auto/feature +fi + + if [ $ngx_found = yes ]; then CORE_LIBS="$CORE_LIBS $ngx_feature_libs" diff --git a/auto/lib/libatomic/conf b/auto/lib/libatomic/conf index d1e484a..dfdc1a6 100644 --- a/auto/lib/libatomic/conf +++ b/auto/lib/libatomic/conf @@ -7,8 +7,8 @@ if [ $NGX_LIBATOMIC != YES ]; then have=NGX_HAVE_LIBATOMIC . auto/have CORE_INCS="$CORE_INCS $NGX_LIBATOMIC/src" - LINK_DEPS="$LINK_DEPS $NGX_LIBATOMIC/src/libatomic_ops.a" - CORE_LIBS="$CORE_LIBS $NGX_LIBATOMIC/src/libatomic_ops.a" + LINK_DEPS="$LINK_DEPS $NGX_LIBATOMIC/build/lib/libatomic_ops.a" + CORE_LIBS="$CORE_LIBS $NGX_LIBATOMIC/build/lib/libatomic_ops.a" else @@ -19,7 +19,7 @@ else #include <atomic_ops.h>" ngx_feature_path= ngx_feature_libs="-latomic_ops" - ngx_feature_test="long n = 0; + ngx_feature_test="AO_t n = 0; if (!AO_compare_and_swap(&n, 0, 1)) return 1; if (AO_fetch_and_add(&n, 1) != 1) diff --git a/auto/lib/libatomic/make b/auto/lib/libatomic/make index c90318e..530c746 100644 --- a/auto/lib/libatomic/make +++ b/auto/lib/libatomic/make @@ -3,14 +3,19 @@ # Copyright (C) Nginx, Inc. + case $NGX_LIBATOMIC in + /*) ngx_prefix="$NGX_LIBATOMIC/build" ;; + *) ngx_prefix="$PWD/$NGX_LIBATOMIC/build" ;; + esac + cat << END >> $NGX_MAKEFILE -$NGX_LIBATOMIC/src/libatomic_ops.a: $NGX_LIBATOMIC/Makefile - cd $NGX_LIBATOMIC && \$(MAKE) +$NGX_LIBATOMIC/build/lib/libatomic_ops.a: $NGX_LIBATOMIC/Makefile + cd $NGX_LIBATOMIC && \$(MAKE) && \$(MAKE) install $NGX_LIBATOMIC/Makefile: $NGX_MAKEFILE cd $NGX_LIBATOMIC \\ && if [ -f Makefile ]; then \$(MAKE) distclean; fi \\ - && ./configure + && ./configure --prefix=$ngx_prefix END diff --git a/auto/lib/libgd/conf b/auto/lib/libgd/conf index 6786397..07f5656 100644 --- a/auto/lib/libgd/conf +++ b/auto/lib/libgd/conf @@ -65,6 +65,23 @@ if [ $ngx_found = no ]; then fi +if [ $ngx_found = no ]; then + + # Homebrew on Apple Silicon + + ngx_feature="GD library in /opt/homebrew/" + ngx_feature_path="/opt/homebrew/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/homebrew/lib -L/opt/homebrew/lib -lgd" + else + ngx_feature_libs="-L/opt/homebrew/lib -lgd" + fi + + . auto/feature +fi + + if [ $ngx_found = yes ]; then CORE_INCS="$CORE_INCS $ngx_feature_path" diff --git a/auto/lib/openssl/conf b/auto/lib/openssl/conf index 4fb52df..fdf430d 100644 --- a/auto/lib/openssl/conf +++ b/auto/lib/openssl/conf @@ -5,12 +5,19 @@ if [ $OPENSSL != NONE ]; then + have=NGX_OPENSSL . auto/have + have=NGX_SSL . auto/have + + have=NGX_OPENSSL_NO_CONFIG . auto/have + + if [ $USE_OPENSSL_QUIC = YES ]; then + have=NGX_QUIC . auto/have + have=NGX_QUIC_OPENSSL_COMPAT . auto/have + fi + case "$CC" in cl | bcc32) - have=NGX_OPENSSL . auto/have - have=NGX_SSL . auto/have - CFLAGS="$CFLAGS -DNO_SYS_TYPES_H" CORE_INCS="$CORE_INCS $OPENSSL/openssl/include" @@ -33,9 +40,6 @@ if [ $OPENSSL != NONE ]; then ;; *) - have=NGX_OPENSSL . auto/have - have=NGX_SSL . auto/have - CORE_INCS="$CORE_INCS $OPENSSL/.openssl/include" CORE_DEPS="$CORE_DEPS $OPENSSL/.openssl/include/openssl/ssl.h" CORE_LIBS="$CORE_LIBS $OPENSSL/.openssl/lib/libssl.a" @@ -118,11 +122,58 @@ else . auto/feature fi + if [ $ngx_found = no ]; then + + # Homebrew on Apple Silicon + + ngx_feature="OpenSSL library in /opt/homebrew/" + ngx_feature_path="/opt/homebrew/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/homebrew/lib -L/opt/homebrew/lib -lssl -lcrypto" + else + ngx_feature_libs="-L/opt/homebrew/lib -lssl -lcrypto" + fi + + ngx_feature_libs="$ngx_feature_libs $NGX_LIBDL $NGX_LIBPTHREAD" + + . auto/feature + fi + if [ $ngx_found = yes ]; then have=NGX_SSL . auto/have CORE_INCS="$CORE_INCS $ngx_feature_path" CORE_LIBS="$CORE_LIBS $ngx_feature_libs" OPENSSL=YES + + if [ $USE_OPENSSL_QUIC = YES ]; then + + ngx_feature="OpenSSL QUIC support" + ngx_feature_name="NGX_QUIC" + ngx_feature_test="SSL_set_quic_method(NULL, NULL)" + . auto/feature + + if [ $ngx_found = no ]; then + have=NGX_QUIC_OPENSSL_COMPAT . auto/have + + ngx_feature="OpenSSL QUIC compatibility" + ngx_feature_test="SSL_CTX_add_custom_ext(NULL, 0, 0, + NULL, NULL, NULL, NULL, NULL)" + . auto/feature + fi + + if [ $ngx_found = no ]; then +cat << END + +$0: error: certain modules require OpenSSL QUIC support. +You can either do not enable the modules, or install the OpenSSL library with +QUIC support into the system, or build the OpenSSL library with QUIC support +statically from the source with nginx by using --with-openssl=<path> option. + +END + exit 1 + fi + fi fi fi diff --git a/auto/lib/openssl/make b/auto/lib/openssl/make index 126a238..a7e9369 100644 --- a/auto/lib/openssl/make +++ b/auto/lib/openssl/make @@ -7,11 +7,24 @@ case "$CC" in cl) + case "$NGX_MACHINE" in + + amd64) + OPENSSL_TARGET=VC-WIN64A + ;; + + *) + OPENSSL_TARGET=VC-WIN32 + ;; + + esac + cat << END >> $NGX_MAKEFILE $OPENSSL/openssl/include/openssl/ssl.h: $NGX_MAKEFILE \$(MAKE) -f auto/lib/openssl/makefile.msvc \ - OPENSSL="$OPENSSL" OPENSSL_OPT="$OPENSSL_OPT" + OPENSSL="$OPENSSL" OPENSSL_OPT="$OPENSSL_OPT" \ + OPENSSL_TARGET="$OPENSSL_TARGET" END diff --git a/auto/lib/openssl/makefile.msvc b/auto/lib/openssl/makefile.msvc index 5b90dcb..ed17cde 100644 --- a/auto/lib/openssl/makefile.msvc +++ b/auto/lib/openssl/makefile.msvc @@ -6,7 +6,7 @@ all: cd $(OPENSSL) - perl Configure VC-WIN32 no-shared \ + perl Configure $(OPENSSL_TARGET) no-shared no-threads \ --prefix="%cd%/openssl" \ --openssldir="%cd%/openssl/ssl" \ $(OPENSSL_OPT) diff --git a/auto/lib/pcre/conf b/auto/lib/pcre/conf index 20c1caf..cdf1809 100644 --- a/auto/lib/pcre/conf +++ b/auto/lib/pcre/conf @@ -182,6 +182,22 @@ else . auto/feature fi + if [ $ngx_found = no ]; then + + # Homebrew on Apple Silicon + + ngx_feature="PCRE library in /opt/homebrew/" + ngx_feature_path="/opt/homebrew/include" + + if [ $NGX_RPATH = YES ]; then + ngx_feature_libs="-R/opt/homebrew/lib -L/opt/homebrew/lib -lpcre" + else + ngx_feature_libs="-L/opt/homebrew/lib -lpcre" + fi + + . auto/feature + fi + if [ $ngx_found = yes ]; then CORE_INCS="$CORE_INCS $ngx_feature_path" CORE_LIBS="$CORE_LIBS $ngx_feature_libs" diff --git a/auto/lib/pcre/make b/auto/lib/pcre/make index 839ef29..182590a 100644 --- a/auto/lib/pcre/make +++ b/auto/lib/pcre/make @@ -36,7 +36,8 @@ if [ $PCRE_LIBRARY = PCRE2 ]; then pcre2_valid_utf.c \ pcre2_xclass.c" - ngx_pcre_test="pcre2_convert.c \ + ngx_pcre_test="pcre2_chkdint.c \ + pcre2_convert.c \ pcre2_extuni.c \ pcre2_find_bracket.c \ pcre2_script_run.c \ diff --git a/auto/make b/auto/make index ef7c9f6..25ee3fb 100644 --- a/auto/make +++ b/auto/make @@ -6,9 +6,10 @@ echo "creating $NGX_MAKEFILE" mkdir -p $NGX_OBJS/src/core $NGX_OBJS/src/event $NGX_OBJS/src/event/modules \ + $NGX_OBJS/src/event/quic \ $NGX_OBJS/src/os/unix $NGX_OBJS/src/os/win32 \ - $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/modules \ - $NGX_OBJS/src/http/modules/perl \ + $NGX_OBJS/src/http $NGX_OBJS/src/http/v2 $NGX_OBJS/src/http/v3 \ + $NGX_OBJS/src/http/modules $NGX_OBJS/src/http/modules/perl \ $NGX_OBJS/src/mail \ $NGX_OBJS/src/stream \ $NGX_OBJS/src/misc diff --git a/auto/modules b/auto/modules index 94867bf..1a5e421 100644 --- a/auto/modules +++ b/auto/modules @@ -102,7 +102,7 @@ if [ $HTTP = YES ]; then fi - if [ $HTTP_V2 = YES ]; then + if [ $HTTP_V2 = YES -o $HTTP_V3 = YES ]; then HTTP_SRCS="$HTTP_SRCS $HTTP_HUFF_SRCS" fi @@ -124,6 +124,7 @@ if [ $HTTP = YES ]; then # ngx_http_header_filter # ngx_http_chunked_filter # ngx_http_v2_filter + # ngx_http_v3_filter # ngx_http_range_header_filter # ngx_http_gzip_filter # ngx_http_postpone_filter @@ -156,6 +157,7 @@ if [ $HTTP = YES ]; then ngx_http_header_filter_module \ ngx_http_chunked_filter_module \ ngx_http_v2_filter_module \ + ngx_http_v3_filter_module \ ngx_http_range_header_filter_module \ ngx_http_gzip_filter_module \ ngx_http_postpone_filter_module \ @@ -217,6 +219,17 @@ if [ $HTTP = YES ]; then . auto/module fi + if [ $HTTP_V3 = YES ]; then + ngx_module_name=ngx_http_v3_filter_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs=src/http/v3/ngx_http_v3_filter_module.c + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + if :; then ngx_module_name=ngx_http_range_header_filter_module ngx_module_incs= @@ -410,7 +423,6 @@ if [ $HTTP = YES ]; then if [ $HTTP_V2 = YES ]; then have=NGX_HTTP_V2 . auto/have - have=NGX_HTTP_HEADERS . auto/have ngx_module_name=ngx_http_v2_module ngx_module_incs=src/http/v2 @@ -426,6 +438,32 @@ if [ $HTTP = YES ]; then . auto/module fi + if [ $HTTP_V3 = YES ]; then + USE_OPENSSL_QUIC=YES + HTTP_SSL=YES + + have=NGX_HTTP_V3 . auto/have + + ngx_module_name=ngx_http_v3_module + ngx_module_incs=src/http/v3 + ngx_module_deps="src/http/v3/ngx_http_v3.h \ + src/http/v3/ngx_http_v3_encode.h \ + src/http/v3/ngx_http_v3_parse.h \ + src/http/v3/ngx_http_v3_table.h \ + src/http/v3/ngx_http_v3_uni.h" + ngx_module_srcs="src/http/v3/ngx_http_v3.c \ + src/http/v3/ngx_http_v3_encode.c \ + src/http/v3/ngx_http_v3_parse.c \ + src/http/v3/ngx_http_v3_table.c \ + src/http/v3/ngx_http_v3_uni.c \ + src/http/v3/ngx_http_v3_request.c \ + src/http/v3/ngx_http_v3_module.c" + ngx_module_libs= + ngx_module_link=$HTTP_V3 + + . auto/module + fi + if :; then ngx_module_name=ngx_http_static_module ngx_module_incs= @@ -1128,6 +1166,16 @@ if [ $STREAM != NO ]; then . auto/module fi + if [ $STREAM_PASS = YES ]; then + ngx_module_name=ngx_stream_pass_module + ngx_module_deps= + ngx_module_srcs=src/stream/ngx_stream_pass_module.c + ngx_module_libs= + ngx_module_link=$STREAM_PASS + + . auto/module + fi + if [ $STREAM_SET = YES ]; then ngx_module_name=ngx_stream_set_module ngx_module_deps= @@ -1272,6 +1320,63 @@ if [ $USE_OPENSSL = YES ]; then fi +if [ $USE_OPENSSL_QUIC = YES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_quic_module + ngx_module_incs= + ngx_module_deps="src/event/quic/ngx_event_quic.h \ + src/event/quic/ngx_event_quic_transport.h \ + src/event/quic/ngx_event_quic_protection.h \ + src/event/quic/ngx_event_quic_connection.h \ + src/event/quic/ngx_event_quic_frames.h \ + src/event/quic/ngx_event_quic_connid.h \ + src/event/quic/ngx_event_quic_migration.h \ + src/event/quic/ngx_event_quic_streams.h \ + src/event/quic/ngx_event_quic_ssl.h \ + src/event/quic/ngx_event_quic_tokens.h \ + src/event/quic/ngx_event_quic_ack.h \ + src/event/quic/ngx_event_quic_output.h \ + src/event/quic/ngx_event_quic_socket.h \ + src/event/quic/ngx_event_quic_openssl_compat.h" + ngx_module_srcs="src/event/quic/ngx_event_quic.c \ + src/event/quic/ngx_event_quic_udp.c \ + src/event/quic/ngx_event_quic_transport.c \ + src/event/quic/ngx_event_quic_protection.c \ + src/event/quic/ngx_event_quic_frames.c \ + src/event/quic/ngx_event_quic_connid.c \ + src/event/quic/ngx_event_quic_migration.c \ + src/event/quic/ngx_event_quic_streams.c \ + src/event/quic/ngx_event_quic_ssl.c \ + src/event/quic/ngx_event_quic_tokens.c \ + src/event/quic/ngx_event_quic_ack.c \ + src/event/quic/ngx_event_quic_output.c \ + src/event/quic/ngx_event_quic_socket.c \ + src/event/quic/ngx_event_quic_openssl_compat.c" + + ngx_module_libs= + ngx_module_link=YES + ngx_module_order= + + . auto/module + + if [ $QUIC_BPF = YES -a $SO_COOKIE_FOUND = YES ]; then + ngx_module_type=CORE + ngx_module_name=ngx_quic_bpf_module + ngx_module_incs= + ngx_module_deps= + ngx_module_srcs="src/event/quic/ngx_event_quic_bpf.c \ + src/event/quic/ngx_event_quic_bpf_code.c" + ngx_module_libs= + ngx_module_link=YES + ngx_module_order= + + . auto/module + + have=NGX_QUIC_BPF . auto/have + fi +fi + + if [ $USE_PCRE = YES ]; then ngx_module_type=CORE ngx_module_name=ngx_regex_module diff --git a/auto/options b/auto/options index 48f3a1a..6a6e990 100644 --- a/auto/options +++ b/auto/options @@ -45,6 +45,8 @@ USE_THREADS=NO NGX_FILE_AIO=NO +QUIC_BPF=NO + HTTP=YES NGX_HTTP_LOG_PATH= @@ -59,6 +61,7 @@ HTTP_CHARSET=YES HTTP_GZIP=YES HTTP_SSL=NO HTTP_V2=NO +HTTP_V3=NO HTTP_SSI=YES HTTP_REALIP=NO HTTP_XSLT=NO @@ -124,6 +127,7 @@ STREAM_GEOIP=NO STREAM_MAP=YES STREAM_SPLIT_CLIENTS=YES STREAM_RETURN=YES +STREAM_PASS=YES STREAM_SET=YES STREAM_UPSTREAM_HASH=YES STREAM_UPSTREAM_LEAST_CONN=YES @@ -149,6 +153,7 @@ PCRE_JIT=NO PCRE2=YES USE_OPENSSL=NO +USE_OPENSSL_QUIC=NO OPENSSL=NONE USE_ZLIB=NO @@ -166,6 +171,8 @@ USE_GEOIP=NO NGX_GOOGLE_PERFTOOLS=NO NGX_CPP_TEST=NO +SO_COOKIE_FOUND=NO + NGX_LIBATOMIC=NO NGX_CPU_CACHE_LINE= @@ -211,6 +218,8 @@ do --with-file-aio) NGX_FILE_AIO=YES ;; + --without-quic_bpf_module) QUIC_BPF=NONE ;; + --with-ipv6) NGX_POST_CONF_MSG="$NGX_POST_CONF_MSG $0: warning: the \"--with-ipv6\" option is deprecated" @@ -228,6 +237,7 @@ $0: warning: the \"--with-ipv6\" option is deprecated" --with-http_ssl_module) HTTP_SSL=YES ;; --with-http_v2_module) HTTP_V2=YES ;; + --with-http_v3_module) HTTP_V3=YES ;; --with-http_realip_module) HTTP_REALIP=YES ;; --with-http_addition_module) HTTP_ADDITION=YES ;; --with-http_xslt_module) HTTP_XSLT=YES ;; @@ -328,6 +338,7 @@ use the \"--with-mail_ssl_module\" option instead" --without-stream_split_clients_module) STREAM_SPLIT_CLIENTS=NO ;; --without-stream_return_module) STREAM_RETURN=NO ;; + --without-stream_pass_module) STREAM_PASS=NO ;; --without-stream_set_module) STREAM_SET=NO ;; --without-stream_upstream_hash_module) STREAM_UPSTREAM_HASH=NO ;; @@ -443,8 +454,11 @@ cat << END --with-file-aio enable file AIO support + --without-quic_bpf_module disable ngx_quic_bpf_module + --with-http_ssl_module enable ngx_http_ssl_module --with-http_v2_module enable ngx_http_v2_module + --with-http_v3_module enable ngx_http_v3_module --with-http_realip_module enable ngx_http_realip_module --with-http_addition_module enable ngx_http_addition_module --with-http_xslt_module enable ngx_http_xslt_module @@ -544,6 +558,7 @@ cat << END --without-stream_split_clients_module disable ngx_stream_split_clients_module --without-stream_return_module disable ngx_stream_return_module + --without-stream_pass_module disable ngx_stream_pass_module --without-stream_set_module disable ngx_stream_set_module --without-stream_upstream_hash_module disable ngx_stream_upstream_hash_module diff --git a/auto/os/conf b/auto/os/conf index 7c6cb69..bb0ce4e 100644 --- a/auto/os/conf +++ b/auto/os/conf @@ -110,11 +110,26 @@ case "$NGX_MACHINE" in NGX_MACH_CACHE_LINE=64 ;; - aarch64 ) + aarch64 | arm64) have=NGX_ALIGNMENT value=16 . auto/define NGX_MACH_CACHE_LINE=64 ;; + ppc64* | powerpc64*) + have=NGX_ALIGNMENT value=16 . auto/define + NGX_MACH_CACHE_LINE=128 + ;; + + riscv64) + have=NGX_ALIGNMENT value=16 . auto/define + NGX_MACH_CACHE_LINE=64 + ;; + + s390x) + have=NGX_ALIGNMENT value=16 . auto/define + NGX_MACH_CACHE_LINE=256 + ;; + *) have=NGX_ALIGNMENT value=16 . auto/define NGX_MACH_CACHE_LINE=32 diff --git a/auto/os/linux b/auto/os/linux index 74b5870..bc0556b 100644 --- a/auto/os/linux +++ b/auto/os/linux @@ -228,8 +228,71 @@ ngx_feature_test="struct crypt_data cd; crypt_r(\"key\", \"salt\", &cd);" . auto/feature +if [ $ngx_found = yes ]; then + CRYPT_LIB="-lcrypt" +fi + ngx_include="sys/vfs.h"; . auto/include +# BPF sockhash + +ngx_feature="BPF sockhash" +ngx_feature_name="NGX_HAVE_BPF" +ngx_feature_run=no +ngx_feature_incs="#include <linux/bpf.h> + #include <sys/syscall.h>" +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="union bpf_attr attr = { 0 }; + + attr.map_flags = 0; + attr.map_type = BPF_MAP_TYPE_SOCKHASH; + + syscall(__NR_bpf, 0, &attr, 0);" +. auto/feature + +if [ $ngx_found = yes ]; then + CORE_SRCS="$CORE_SRCS src/core/ngx_bpf.c" + CORE_DEPS="$CORE_DEPS src/core/ngx_bpf.h" + + if [ $QUIC_BPF != NONE ]; then + QUIC_BPF=YES + fi +fi + + +ngx_feature="SO_COOKIE" +ngx_feature_name="NGX_HAVE_SO_COOKIE" +ngx_feature_run=no +ngx_feature_incs="#include <sys/socket.h> + $NGX_INCLUDE_INTTYPES_H" +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="socklen_t optlen = sizeof(uint64_t); + uint64_t cookie; + getsockopt(0, SOL_SOCKET, SO_COOKIE, &cookie, &optlen)" +. auto/feature + +if [ $ngx_found = yes ]; then + SO_COOKIE_FOUND=YES +fi + + +# UDP segmentation offloading + +ngx_feature="UDP_SEGMENT" +ngx_feature_name="NGX_HAVE_UDP_SEGMENT" +ngx_feature_run=no +ngx_feature_incs="#include <sys/socket.h> + #include <netinet/udp.h>" +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="socklen_t optlen = sizeof(int); + int val; + getsockopt(0, SOL_UDP, UDP_SEGMENT, &val, &optlen)" +. auto/feature + + CC_AUX_FLAGS="$cc_aux_flags -D_GNU_SOURCE -D_FILE_OFFSET_BITS=64" diff --git a/auto/os/win32 b/auto/os/win32 index b821ae6..bce764b 100644 --- a/auto/os/win32 +++ b/auto/os/win32 @@ -18,7 +18,7 @@ ngx_binext=".exe" case "$NGX_CC_NAME" in - gcc) + clang | gcc) CORE_LIBS="$CORE_LIBS -ladvapi32 -lws2_32" MAIN_LINK="$MAIN_LINK -Wl,--export-all-symbols" MAIN_LINK="$MAIN_LINK -Wl,--out-implib=$NGX_OBJS/libnginx.a" diff --git a/auto/sources b/auto/sources index 156f797..46408ee 100644 --- a/auto/sources +++ b/auto/sources @@ -83,13 +83,14 @@ CORE_SRCS="src/core/nginx.c \ EVENT_MODULES="ngx_events_module ngx_event_core_module" -EVENT_INCS="src/event src/event/modules" +EVENT_INCS="src/event src/event/modules src/event/quic" EVENT_DEPS="src/event/ngx_event.h \ src/event/ngx_event_timer.h \ src/event/ngx_event_posted.h \ src/event/ngx_event_connect.h \ - src/event/ngx_event_pipe.h" + src/event/ngx_event_pipe.h \ + src/event/ngx_event_udp.h" EVENT_SRCS="src/event/ngx_event.c \ src/event/ngx_event_timer.c \ diff --git a/auto/unix b/auto/unix index 8671019..f29e69c 100644 --- a/auto/unix +++ b/auto/unix @@ -448,6 +448,54 @@ ngx_feature_test="setsockopt(0, IPPROTO_IPV6, IPV6_RECVPKTINFO, NULL, 0)" . auto/feature +# IP packet fragmentation + +ngx_feature="IP_MTU_DISCOVER" +ngx_feature_name="NGX_HAVE_IP_MTU_DISCOVER" +ngx_feature_run=no +ngx_feature_incs="#include <sys/socket.h> + #include <netinet/in.h>" +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="(void) IP_PMTUDISC_DO; + setsockopt(0, IPPROTO_IP, IP_MTU_DISCOVER, NULL, 0)" +. auto/feature + + +ngx_feature="IPV6_MTU_DISCOVER" +ngx_feature_name="NGX_HAVE_IPV6_MTU_DISCOVER" +ngx_feature_run=no +ngx_feature_incs="#include <sys/socket.h> + #include <netinet/in.h>" +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="(void) IPV6_PMTUDISC_DO; + setsockopt(0, IPPROTO_IPV6, IPV6_MTU_DISCOVER, NULL, 0)" +. auto/feature + + +ngx_feature="IP_DONTFRAG" +ngx_feature_name="NGX_HAVE_IP_DONTFRAG" +ngx_feature_run=no +ngx_feature_incs="#include <sys/socket.h> + #include <netinet/in.h>" +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, IPPROTO_IP, IP_DONTFRAG, NULL, 0)" +. auto/feature + + +ngx_feature="IPV6_DONTFRAG" +ngx_feature_name="NGX_HAVE_IPV6_DONTFRAG" +ngx_feature_run=no +ngx_feature_incs="#include <sys/socket.h> + #include <netinet/in.h>" +ngx_feature_path= +ngx_feature_libs= +ngx_feature_test="setsockopt(0, IPPROTO_IP, IPV6_DONTFRAG, NULL, 0)" +. auto/feature + + ngx_feature="TCP_DEFER_ACCEPT" ngx_feature_name="NGX_HAVE_DEFERRED_ACCEPT" ngx_feature_run=no diff --git a/configure b/configure index 474d69e..5b88ebb 100755 --- a/configure +++ b/configure @@ -44,6 +44,7 @@ if test -z "$NGX_PLATFORM"; then else echo "building for $NGX_PLATFORM" NGX_SYSTEM=$NGX_PLATFORM + NGX_MACHINE=i386 fi . auto/cc/conf diff --git a/contrib/vim/syntax/nginx.vim b/contrib/vim/syntax/nginx.vim index 6828cd3..29eef7a 100644 --- a/contrib/vim/syntax/nginx.vim +++ b/contrib/vim/syntax/nginx.vim @@ -65,12 +65,12 @@ syn match ngxListenComment '#.*$' \ contained \ nextgroup=@ngxListenParams skipwhite skipempty syn keyword ngxListenOptions contained - \ default_server ssl http2 proxy_protocol + \ default_server ssl quic proxy_protocol \ setfib fastopen backlog rcvbuf sndbuf accept_filter deferred bind \ ipv6only reuseport so_keepalive \ nextgroup=@ngxListenParams skipwhite skipempty syn keyword ngxListenOptionsDeprecated contained - \ spdy + \ http2 \ nextgroup=@ngxListenParams skipwhite skipempty syn cluster ngxListenParams \ contains=ngxListenParam,ngxListenString,ngxListenComment @@ -90,7 +90,6 @@ syn keyword ngxDirectiveBlock contained if syn keyword ngxDirectiveBlock contained geo syn keyword ngxDirectiveBlock contained map syn keyword ngxDirectiveBlock contained split_clients -syn keyword ngxDirectiveBlock contained match syn keyword ngxDirectiveImportant contained include syn keyword ngxDirectiveImportant contained root @@ -111,19 +110,13 @@ syn keyword ngxDirectiveControl contained set syn keyword ngxDirectiveError contained error_page syn keyword ngxDirectiveError contained post_action -syn keyword ngxDirectiveDeprecated contained limit_zone syn keyword ngxDirectiveDeprecated contained proxy_downstream_buffer syn keyword ngxDirectiveDeprecated contained proxy_upstream_buffer -syn keyword ngxDirectiveDeprecated contained spdy_chunk_size -syn keyword ngxDirectiveDeprecated contained spdy_headers_comp -syn keyword ngxDirectiveDeprecated contained spdy_keepalive_timeout -syn keyword ngxDirectiveDeprecated contained spdy_max_concurrent_streams -syn keyword ngxDirectiveDeprecated contained spdy_pool_size -syn keyword ngxDirectiveDeprecated contained spdy_recv_buffer_size -syn keyword ngxDirectiveDeprecated contained spdy_recv_timeout -syn keyword ngxDirectiveDeprecated contained spdy_streams_index_size -syn keyword ngxDirectiveDeprecated contained ssl -syn keyword ngxDirectiveDeprecated contained upstream_conf +syn keyword ngxDirectiveDeprecated contained http2_idle_timeout +syn keyword ngxDirectiveDeprecated contained http2_max_field_size +syn keyword ngxDirectiveDeprecated contained http2_max_header_size +syn keyword ngxDirectiveDeprecated contained http2_max_requests +syn keyword ngxDirectiveDeprecated contained http2_recv_timeout syn keyword ngxDirective contained absolute_redirect syn keyword ngxDirective contained accept_mutex @@ -141,7 +134,6 @@ syn keyword ngxDirective contained alias syn keyword ngxDirective contained allow syn keyword ngxDirective contained ancient_browser syn keyword ngxDirective contained ancient_browser_value -syn keyword ngxDirective contained api syn keyword ngxDirective contained auth_basic syn keyword ngxDirective contained auth_basic_user_file syn keyword ngxDirective contained auth_delay @@ -149,14 +141,6 @@ syn keyword ngxDirective contained auth_http syn keyword ngxDirective contained auth_http_header syn keyword ngxDirective contained auth_http_pass_client_cert syn keyword ngxDirective contained auth_http_timeout -syn keyword ngxDirective contained auth_jwt -syn keyword ngxDirective contained auth_jwt_claim_set -syn keyword ngxDirective contained auth_jwt_header_set -syn keyword ngxDirective contained auth_jwt_key_file -syn keyword ngxDirective contained auth_jwt_key_request -syn keyword ngxDirective contained auth_jwt_leeway -syn keyword ngxDirective contained auth_jwt_require -syn keyword ngxDirective contained auth_jwt_type syn keyword ngxDirective contained auth_request syn keyword ngxDirective contained auth_request_set syn keyword ngxDirective contained autoindex @@ -197,8 +181,6 @@ syn keyword ngxDirective contained error_log syn keyword ngxDirective contained etag syn keyword ngxDirective contained eventport_events syn keyword ngxDirective contained expires -syn keyword ngxDirective contained f4f -syn keyword ngxDirective contained f4f_buffer_size syn keyword ngxDirective contained fastcgi_bind syn keyword ngxDirective contained fastcgi_buffer_size syn keyword ngxDirective contained fastcgi_buffering @@ -215,7 +197,6 @@ syn keyword ngxDirective contained fastcgi_cache_max_range_offset syn keyword ngxDirective contained fastcgi_cache_methods syn keyword ngxDirective contained fastcgi_cache_min_uses syn keyword ngxDirective contained fastcgi_cache_path -syn keyword ngxDirective contained fastcgi_cache_purge syn keyword ngxDirective contained fastcgi_cache_revalidate syn keyword ngxDirective contained fastcgi_cache_use_stale syn keyword ngxDirective contained fastcgi_cache_valid @@ -299,28 +280,20 @@ syn keyword ngxDirective contained gzip_types syn keyword ngxDirective contained gzip_vary syn keyword ngxDirective contained gzip_window syn keyword ngxDirective contained hash -syn keyword ngxDirective contained health_check -syn keyword ngxDirective contained health_check_timeout -syn keyword ngxDirective contained hls -syn keyword ngxDirective contained hls_buffers -syn keyword ngxDirective contained hls_forward_args -syn keyword ngxDirective contained hls_fragment -syn keyword ngxDirective contained hls_mp4_buffer_size -syn keyword ngxDirective contained hls_mp4_max_buffer_size +syn keyword ngxDirective contained http2 syn keyword ngxDirective contained http2_body_preread_size syn keyword ngxDirective contained http2_chunk_size -syn keyword ngxDirective contained http2_idle_timeout syn keyword ngxDirective contained http2_max_concurrent_pushes syn keyword ngxDirective contained http2_max_concurrent_streams -syn keyword ngxDirective contained http2_max_field_size -syn keyword ngxDirective contained http2_max_header_size -syn keyword ngxDirective contained http2_max_requests syn keyword ngxDirective contained http2_pool_size syn keyword ngxDirective contained http2_push syn keyword ngxDirective contained http2_push_preload syn keyword ngxDirective contained http2_recv_buffer_size -syn keyword ngxDirective contained http2_recv_timeout syn keyword ngxDirective contained http2_streams_index_size +syn keyword ngxDirective contained http3 +syn keyword ngxDirective contained http3_hq +syn keyword ngxDirective contained http3_max_concurrent_streams +syn keyword ngxDirective contained http3_stream_buffer_size syn keyword ngxDirective contained if_modified_since syn keyword ngxDirective contained ignore_invalid_headers syn keyword ngxDirective contained image_filter @@ -339,30 +312,32 @@ syn keyword ngxDirective contained ip_hash syn keyword ngxDirective contained js_access syn keyword ngxDirective contained js_body_filter syn keyword ngxDirective contained js_content +syn keyword ngxDirective contained js_fetch_buffer_size syn keyword ngxDirective contained js_fetch_ciphers +syn keyword ngxDirective contained js_fetch_max_response_buffer_size syn keyword ngxDirective contained js_fetch_protocols +syn keyword ngxDirective contained js_fetch_timeout syn keyword ngxDirective contained js_fetch_trusted_certificate +syn keyword ngxDirective contained js_fetch_verify syn keyword ngxDirective contained js_fetch_verify_depth syn keyword ngxDirective contained js_filter syn keyword ngxDirective contained js_header_filter syn keyword ngxDirective contained js_import -syn keyword ngxDirective contained js_include syn keyword ngxDirective contained js_path +syn keyword ngxDirective contained js_preload_object syn keyword ngxDirective contained js_preread syn keyword ngxDirective contained js_set +syn keyword ngxDirective contained js_shared_dict_zone syn keyword ngxDirective contained js_var syn keyword ngxDirective contained keepalive syn keyword ngxDirective contained keepalive_disable syn keyword ngxDirective contained keepalive_requests syn keyword ngxDirective contained keepalive_time syn keyword ngxDirective contained keepalive_timeout -syn keyword ngxDirective contained keyval -syn keyword ngxDirective contained keyval_zone syn keyword ngxDirective contained kqueue_changes syn keyword ngxDirective contained kqueue_events syn keyword ngxDirective contained large_client_header_buffers syn keyword ngxDirective contained least_conn -syn keyword ngxDirective contained least_time syn keyword ngxDirective contained limit_conn syn keyword ngxDirective contained limit_conn_dry_run syn keyword ngxDirective contained limit_conn_log_level @@ -391,7 +366,6 @@ syn keyword ngxDirective contained max_ranges syn keyword ngxDirective contained memcached_bind syn keyword ngxDirective contained memcached_buffer_size syn keyword ngxDirective contained memcached_connect_timeout -syn keyword ngxDirective contained memcached_force_ranges syn keyword ngxDirective contained memcached_gzip_flag syn keyword ngxDirective contained memcached_next_upstream syn keyword ngxDirective contained memcached_next_upstream_timeout @@ -407,14 +381,11 @@ syn keyword ngxDirective contained modern_browser syn keyword ngxDirective contained modern_browser_value syn keyword ngxDirective contained mp4 syn keyword ngxDirective contained mp4_buffer_size -syn keyword ngxDirective contained mp4_limit_rate -syn keyword ngxDirective contained mp4_limit_rate_after syn keyword ngxDirective contained mp4_max_buffer_size syn keyword ngxDirective contained mp4_start_key_frame syn keyword ngxDirective contained msie_padding syn keyword ngxDirective contained msie_refresh syn keyword ngxDirective contained multi_accept -syn keyword ngxDirective contained ntlm syn keyword ngxDirective contained open_file_cache syn keyword ngxDirective contained open_file_cache_errors syn keyword ngxDirective contained open_file_cache_events @@ -457,7 +428,6 @@ syn keyword ngxDirective contained proxy_cache_max_range_offset syn keyword ngxDirective contained proxy_cache_methods syn keyword ngxDirective contained proxy_cache_min_uses syn keyword ngxDirective contained proxy_cache_path -syn keyword ngxDirective contained proxy_cache_purge syn keyword ngxDirective contained proxy_cache_revalidate syn keyword ngxDirective contained proxy_cache_use_stale syn keyword ngxDirective contained proxy_cache_valid @@ -495,7 +465,6 @@ syn keyword ngxDirective contained proxy_requests syn keyword ngxDirective contained proxy_responses syn keyword ngxDirective contained proxy_send_lowat syn keyword ngxDirective contained proxy_send_timeout -syn keyword ngxDirective contained proxy_session_drop syn keyword ngxDirective contained proxy_set_body syn keyword ngxDirective contained proxy_set_header syn keyword ngxDirective contained proxy_smtp_auth @@ -520,7 +489,11 @@ syn keyword ngxDirective contained proxy_temp_file_write_size syn keyword ngxDirective contained proxy_temp_path syn keyword ngxDirective contained proxy_timeout syn keyword ngxDirective contained proxy_upload_rate -syn keyword ngxDirective contained queue +syn keyword ngxDirective contained quic_active_connection_id_limit +syn keyword ngxDirective contained quic_bpf +syn keyword ngxDirective contained quic_gso +syn keyword ngxDirective contained quic_host_key +syn keyword ngxDirective contained quic_retry syn keyword ngxDirective contained random syn keyword ngxDirective contained random_index syn keyword ngxDirective contained read_ahead @@ -551,7 +524,6 @@ syn keyword ngxDirective contained scgi_cache_max_range_offset syn keyword ngxDirective contained scgi_cache_methods syn keyword ngxDirective contained scgi_cache_min_uses syn keyword ngxDirective contained scgi_cache_path -syn keyword ngxDirective contained scgi_cache_purge syn keyword ngxDirective contained scgi_cache_revalidate syn keyword ngxDirective contained scgi_cache_use_stale syn keyword ngxDirective contained scgi_cache_valid @@ -590,9 +562,6 @@ syn keyword ngxDirective contained server_name_in_redirect syn keyword ngxDirective contained server_names_hash_bucket_size syn keyword ngxDirective contained server_names_hash_max_size syn keyword ngxDirective contained server_tokens -syn keyword ngxDirective contained session_log -syn keyword ngxDirective contained session_log_format -syn keyword ngxDirective contained session_log_zone syn keyword ngxDirective contained set_real_ip_from syn keyword ngxDirective contained slice syn keyword ngxDirective contained smtp_auth @@ -640,12 +609,6 @@ syn keyword ngxDirective contained ssl_trusted_certificate syn keyword ngxDirective contained ssl_verify_client syn keyword ngxDirective contained ssl_verify_depth syn keyword ngxDirective contained starttls -syn keyword ngxDirective contained state -syn keyword ngxDirective contained status -syn keyword ngxDirective contained status_format -syn keyword ngxDirective contained status_zone -syn keyword ngxDirective contained sticky -syn keyword ngxDirective contained sticky_cookie_insert syn keyword ngxDirective contained stub_status syn keyword ngxDirective contained sub_filter syn keyword ngxDirective contained sub_filter_last_modified @@ -688,7 +651,6 @@ syn keyword ngxDirective contained uwsgi_cache_max_range_offset syn keyword ngxDirective contained uwsgi_cache_methods syn keyword ngxDirective contained uwsgi_cache_min_uses syn keyword ngxDirective contained uwsgi_cache_path -syn keyword ngxDirective contained uwsgi_cache_purge syn keyword ngxDirective contained uwsgi_cache_revalidate syn keyword ngxDirective contained uwsgi_cache_use_stale syn keyword ngxDirective contained uwsgi_cache_valid @@ -752,6 +714,62 @@ syn keyword ngxDirective contained xslt_string_param syn keyword ngxDirective contained xslt_stylesheet syn keyword ngxDirective contained xslt_types syn keyword ngxDirective contained zone + +" nginx-plus commercial extensions directives + +syn keyword ngxDirectiveBlock contained match +syn keyword ngxDirectiveBlock contained otel_exporter + +syn keyword ngxDirective contained api +syn keyword ngxDirective contained auth_jwt +syn keyword ngxDirective contained auth_jwt_claim_set +syn keyword ngxDirective contained auth_jwt_header_set +syn keyword ngxDirective contained auth_jwt_key_cache +syn keyword ngxDirective contained auth_jwt_key_file +syn keyword ngxDirective contained auth_jwt_key_request +syn keyword ngxDirective contained auth_jwt_leeway +syn keyword ngxDirective contained auth_jwt_require +syn keyword ngxDirective contained auth_jwt_type +syn keyword ngxDirective contained f4f +syn keyword ngxDirective contained f4f_buffer_size +syn keyword ngxDirective contained fastcgi_cache_purge +syn keyword ngxDirective contained health_check +syn keyword ngxDirective contained health_check_timeout +syn keyword ngxDirective contained hls +syn keyword ngxDirective contained hls_buffers +syn keyword ngxDirective contained hls_forward_args +syn keyword ngxDirective contained hls_fragment +syn keyword ngxDirective contained hls_mp4_buffer_size +syn keyword ngxDirective contained hls_mp4_max_buffer_size +syn keyword ngxDirective contained internal_redirect +syn keyword ngxDirective contained keyval +syn keyword ngxDirective contained keyval_zone +syn keyword ngxDirective contained least_time +syn keyword ngxDirective contained mp4_limit_rate +syn keyword ngxDirective contained mp4_limit_rate_after +syn keyword ngxDirective contained mqtt +syn keyword ngxDirective contained mqtt_preread +syn keyword ngxDirective contained mqtt_rewrite_buffer_size +syn keyword ngxDirective contained mqtt_set_connect +syn keyword ngxDirective contained ntlm +syn keyword ngxDirective contained otel_service_name +syn keyword ngxDirective contained otel_span_attr +syn keyword ngxDirective contained otel_span_name +syn keyword ngxDirective contained otel_trace +syn keyword ngxDirective contained otel_trace_context +syn keyword ngxDirective contained proxy_cache_purge +syn keyword ngxDirective contained proxy_session_drop +syn keyword ngxDirective contained queue +syn keyword ngxDirective contained scgi_cache_purge +syn keyword ngxDirective contained session_log +syn keyword ngxDirective contained session_log_format +syn keyword ngxDirective contained session_log_zone +syn keyword ngxDirective contained state +syn keyword ngxDirective contained status +syn keyword ngxDirective contained status_format +syn keyword ngxDirective contained status_zone +syn keyword ngxDirective contained sticky +syn keyword ngxDirective contained uwsgi_cache_purge syn keyword ngxDirective contained zone_sync syn keyword ngxDirective contained zone_sync_buffers syn keyword ngxDirective contained zone_sync_connect_retry_interval @@ -775,61 +793,12 @@ syn keyword ngxDirective contained zone_sync_ssl_verify_depth syn keyword ngxDirective contained zone_sync_timeout " 3rd party modules list taken from -" https://github.com/freebsd/freebsd-ports/blob/master/www/nginx-devel/Makefile -" ----------------------------------------------------------------------------- - -" Accept Language -" https://github.com/giom/nginx_accept_language_module -syn keyword ngxDirectiveThirdParty contained set_from_accept_language +" https://github.com/freebsd/freebsd-ports/blob/main/www/nginx-devel/Makefile.extmod +" ---------------------------------------------------------------------------------- -" Digest Authentication -" https://github.com/atomx/nginx-http-auth-digest -syn keyword ngxDirectiveThirdParty contained auth_digest -syn keyword ngxDirectiveThirdParty contained auth_digest_drop_time -syn keyword ngxDirectiveThirdParty contained auth_digest_evasion_time -syn keyword ngxDirectiveThirdParty contained auth_digest_expires -syn keyword ngxDirectiveThirdParty contained auth_digest_maxtries -syn keyword ngxDirectiveThirdParty contained auth_digest_replays -syn keyword ngxDirectiveThirdParty contained auth_digest_shm_size -syn keyword ngxDirectiveThirdParty contained auth_digest_timeout -syn keyword ngxDirectiveThirdParty contained auth_digest_user_file - -" SPNEGO Authentication -" https://github.com/stnoonan/spnego-http-auth-nginx-module -syn keyword ngxDirectiveThirdParty contained auth_gss -syn keyword ngxDirectiveThirdParty contained auth_gss_allow_basic_fallback -syn keyword ngxDirectiveThirdParty contained auth_gss_authorized_principal -syn keyword ngxDirectiveThirdParty contained auth_gss_authorized_principal_regex -syn keyword ngxDirectiveThirdParty contained auth_gss_constrained_delegation -syn keyword ngxDirectiveThirdParty contained auth_gss_delegate_credentials -syn keyword ngxDirectiveThirdParty contained auth_gss_force_realm -syn keyword ngxDirectiveThirdParty contained auth_gss_format_full -syn keyword ngxDirectiveThirdParty contained auth_gss_keytab -syn keyword ngxDirectiveThirdParty contained auth_gss_map_to_local -syn keyword ngxDirectiveThirdParty contained auth_gss_realm -syn keyword ngxDirectiveThirdParty contained auth_gss_service_ccache -syn keyword ngxDirectiveThirdParty contained auth_gss_service_name - -" LDAP Authentication -" https://github.com/kvspb/nginx-auth-ldap -syn keyword ngxDirectiveThirdParty contained auth_ldap -syn keyword ngxDirectiveThirdParty contained auth_ldap_cache_enabled -syn keyword ngxDirectiveThirdParty contained auth_ldap_cache_expiration_time -syn keyword ngxDirectiveThirdParty contained auth_ldap_cache_size -syn keyword ngxDirectiveThirdParty contained auth_ldap_servers -syn keyword ngxDirectiveThirdParty contained auth_ldap_servers_size -syn keyword ngxDirectiveThirdParty contained ldap_server - -" PAM Authentication -" https://github.com/sto/ngx_http_auth_pam_module -syn keyword ngxDirectiveThirdParty contained auth_pam -syn keyword ngxDirectiveThirdParty contained auth_pam_service_name -syn keyword ngxDirectiveThirdParty contained auth_pam_set_pam_env - -" AJP protocol proxy -" https://github.com/yaoweibin/nginx_ajp_module -syn keyword ngxDirectiveThirdParty contained ajp_buffers +" https://github.com/msva/nginx_ajp_module syn keyword ngxDirectiveThirdParty contained ajp_buffer_size +syn keyword ngxDirectiveThirdParty contained ajp_buffers syn keyword ngxDirectiveThirdParty contained ajp_busy_buffers_size syn keyword ngxDirectiveThirdParty contained ajp_cache syn keyword ngxDirectiveThirdParty contained ajp_cache_key @@ -850,11 +819,13 @@ syn keyword ngxDirectiveThirdParty contained ajp_keep_conn syn keyword ngxDirectiveThirdParty contained ajp_max_data_packet_size syn keyword ngxDirectiveThirdParty contained ajp_max_temp_file_size syn keyword ngxDirectiveThirdParty contained ajp_next_upstream +syn keyword ngxDirectiveThirdParty contained ajp_param syn keyword ngxDirectiveThirdParty contained ajp_pass syn keyword ngxDirectiveThirdParty contained ajp_pass_header syn keyword ngxDirectiveThirdParty contained ajp_pass_request_body syn keyword ngxDirectiveThirdParty contained ajp_pass_request_headers syn keyword ngxDirectiveThirdParty contained ajp_read_timeout +syn keyword ngxDirectiveThirdParty contained ajp_script_url syn keyword ngxDirectiveThirdParty contained ajp_secret syn keyword ngxDirectiveThirdParty contained ajp_send_lowat syn keyword ngxDirectiveThirdParty contained ajp_send_timeout @@ -865,7 +836,12 @@ syn keyword ngxDirectiveThirdParty contained ajp_temp_path syn keyword ngxDirectiveThirdParty contained ajp_upstream_fail_timeout syn keyword ngxDirectiveThirdParty contained ajp_upstream_max_fails -" AWS proxy +" https://github.com/openresty/array-var-nginx-module +syn keyword ngxDirectiveThirdParty contained array_join +syn keyword ngxDirectiveThirdParty contained array_map +syn keyword ngxDirectiveThirdParty contained array_map_op +syn keyword ngxDirectiveThirdParty contained array_split + " https://github.com/anomalizer/ngx_aws_auth syn keyword ngxDirectiveThirdParty contained aws_access_key syn keyword ngxDirectiveThirdParty contained aws_endpoint @@ -874,65 +850,32 @@ syn keyword ngxDirectiveThirdParty contained aws_s3_bucket syn keyword ngxDirectiveThirdParty contained aws_sign syn keyword ngxDirectiveThirdParty contained aws_signing_key -" embedding Clojure or Java or Groovy programs -" https://github.com/nginx-clojure/nginx-clojure -syn keyword ngxDirectiveThirdParty contained access_handler_code -syn keyword ngxDirectiveThirdParty contained access_handler_name -syn keyword ngxDirectiveThirdParty contained access_handler_property -syn keyword ngxDirectiveThirdParty contained access_handler_type -syn keyword ngxDirectiveThirdParty contained always_read_body -syn keyword ngxDirectiveThirdParty contained auto_upgrade_ws -syn keyword ngxDirectiveThirdParty contained body_filter_code -syn keyword ngxDirectiveThirdParty contained body_filter_name -syn keyword ngxDirectiveThirdParty contained body_filter_property -syn keyword ngxDirectiveThirdParty contained body_filter_type -syn keyword ngxDirectiveThirdParty contained content_handler_code -syn keyword ngxDirectiveThirdParty contained content_handler_name -syn keyword ngxDirectiveThirdParty contained content_handler_property -syn keyword ngxDirectiveThirdParty contained content_handler_type -syn keyword ngxDirectiveThirdParty contained handler_code -syn keyword ngxDirectiveThirdParty contained handler_name -syn keyword ngxDirectiveThirdParty contained handlers_lazy_init -syn keyword ngxDirectiveThirdParty contained handler_type -syn keyword ngxDirectiveThirdParty contained header_filter_code -syn keyword ngxDirectiveThirdParty contained header_filter_name -syn keyword ngxDirectiveThirdParty contained header_filter_property -syn keyword ngxDirectiveThirdParty contained header_filter_type -syn keyword ngxDirectiveThirdParty contained jvm_classpath -syn keyword ngxDirectiveThirdParty contained jvm_classpath_check -syn keyword ngxDirectiveThirdParty contained jvm_exit_handler_code -syn keyword ngxDirectiveThirdParty contained jvm_exit_handler_name -syn keyword ngxDirectiveThirdParty contained jvm_handler_type -syn keyword ngxDirectiveThirdParty contained jvm_init_handler_code -syn keyword ngxDirectiveThirdParty contained jvm_init_handler_name -syn keyword ngxDirectiveThirdParty contained jvm_options -syn keyword ngxDirectiveThirdParty contained jvm_path -syn keyword ngxDirectiveThirdParty contained jvm_var -syn keyword ngxDirectiveThirdParty contained jvm_workers -syn keyword ngxDirectiveThirdParty contained log_handler_code -syn keyword ngxDirectiveThirdParty contained log_handler_name -syn keyword ngxDirectiveThirdParty contained log_handler_property -syn keyword ngxDirectiveThirdParty contained log_handler_type -syn keyword ngxDirectiveThirdParty contained max_balanced_tcp_connections -syn keyword ngxDirectiveThirdParty contained rewrite_handler_code -syn keyword ngxDirectiveThirdParty contained rewrite_handler_name -syn keyword ngxDirectiveThirdParty contained rewrite_handler_property -syn keyword ngxDirectiveThirdParty contained rewrite_handler_type -syn keyword ngxDirectiveThirdParty contained shared_map -syn keyword ngxDirectiveThirdParty contained write_page_size - - -" Certificate Transparency +" https://github.com/google/ngx_brotli +syn keyword ngxDirectiveThirdParty contained brotli +syn keyword ngxDirectiveThirdParty contained brotli_buffers +syn keyword ngxDirectiveThirdParty contained brotli_comp_level +syn keyword ngxDirectiveThirdParty contained brotli_min_length +syn keyword ngxDirectiveThirdParty contained brotli_static +syn keyword ngxDirectiveThirdParty contained brotli_types +syn keyword ngxDirectiveThirdParty contained brotli_window + +" https://github.com/torden/ngx_cache_purge +syn keyword ngxDirectiveThirdParty contained cache_purge_response_type + +" https://github.com/AirisX/nginx_cookie_flag_module +syn keyword ngxDirectiveThirdParty contained set_cookie_flag + " https://github.com/grahamedgecombe/nginx-ct syn keyword ngxDirectiveThirdParty contained ssl_ct syn keyword ngxDirectiveThirdParty contained ssl_ct_static_scts -" ngx_echo " https://github.com/openresty/echo-nginx-module +syn keyword ngxDirectiveThirdParty contained echo syn keyword ngxDirectiveThirdParty contained echo_abort_parent syn keyword ngxDirectiveThirdParty contained echo_after_body syn keyword ngxDirectiveThirdParty contained echo_before_body syn keyword ngxDirectiveThirdParty contained echo_blocking_sleep +syn keyword ngxDirectiveThirdParty contained echo_duplicate syn keyword ngxDirectiveThirdParty contained echo_end syn keyword ngxDirectiveThirdParty contained echo_exec syn keyword ngxDirectiveThirdParty contained echo_flush @@ -942,28 +885,102 @@ syn keyword ngxDirectiveThirdParty contained echo_location_async syn keyword ngxDirectiveThirdParty contained echo_read_request_body syn keyword ngxDirectiveThirdParty contained echo_request_body syn keyword ngxDirectiveThirdParty contained echo_reset_timer +syn keyword ngxDirectiveThirdParty contained echo_sleep syn keyword ngxDirectiveThirdParty contained echo_status syn keyword ngxDirectiveThirdParty contained echo_subrequest syn keyword ngxDirectiveThirdParty contained echo_subrequest_async -" FastDFS -" https://github.com/happyfish100/fastdfs-nginx-module -syn keyword ngxDirectiveThirdParty contained ngx_fastdfs_module +" https://github.com/openresty/drizzle-nginx-module +syn keyword ngxDirectiveThirdParty contained drizzle_buffer_size +syn keyword ngxDirectiveThirdParty contained drizzle_connect_timeout +syn keyword ngxDirectiveThirdParty contained drizzle_dbname +syn keyword ngxDirectiveThirdParty contained drizzle_keepalive +syn keyword ngxDirectiveThirdParty contained drizzle_module_header +syn keyword ngxDirectiveThirdParty contained drizzle_pass +syn keyword ngxDirectiveThirdParty contained drizzle_query +syn keyword ngxDirectiveThirdParty contained drizzle_recv_cols_timeout +syn keyword ngxDirectiveThirdParty contained drizzle_recv_rows_timeout +syn keyword ngxDirectiveThirdParty contained drizzle_send_query_timeout +syn keyword ngxDirectiveThirdParty contained drizzle_server +syn keyword ngxDirectiveThirdParty contained drizzle_status + +" https://github.com/ZigzagAK/ngx_dynamic_upstream +syn keyword ngxDirectiveThirdParty contained dns_add_down +syn keyword ngxDirectiveThirdParty contained dns_ipv6 +syn keyword ngxDirectiveThirdParty contained dns_update +syn keyword ngxDirectiveThirdParty contained dynamic_state_file +syn keyword ngxDirectiveThirdParty contained dynamic_upstream + +" https://github.com/openresty/encrypted-session-nginx-module +syn keyword ngxDirectiveThirdParty contained encrypted_session_expires +syn keyword ngxDirectiveThirdParty contained encrypted_session_iv +syn keyword ngxDirectiveThirdParty contained encrypted_session_key +syn keyword ngxDirectiveThirdParty contained set_decrypt_session +syn keyword ngxDirectiveThirdParty contained set_encrypt_session + +" https://github.com/calio/form-input-nginx-module +syn keyword ngxDirectiveThirdParty contained set_form_input +syn keyword ngxDirectiveThirdParty contained set_form_input_multi + +" https://github.com/nieoding/nginx-gridfs +syn keyword ngxDirectiveThirdParty contained gridfs +syn keyword ngxDirectiveThirdParty contained mongo -" ngx_headers_more " https://github.com/openresty/headers-more-nginx-module syn keyword ngxDirectiveThirdParty contained more_clear_headers syn keyword ngxDirectiveThirdParty contained more_clear_input_headers syn keyword ngxDirectiveThirdParty contained more_set_headers syn keyword ngxDirectiveThirdParty contained more_set_input_headers -" NGINX WebDAV missing commands support (PROPFIND & OPTIONS) +" https://github.com/dvershinin/nginx_accept_language_module +syn keyword ngxDirectiveThirdParty contained set_from_accept_language + +" https://github.com/atomx/nginx-http-auth-digest +syn keyword ngxDirectiveThirdParty contained auth_digest +syn keyword ngxDirectiveThirdParty contained auth_digest_drop_time +syn keyword ngxDirectiveThirdParty contained auth_digest_evasion_time +syn keyword ngxDirectiveThirdParty contained auth_digest_expires +syn keyword ngxDirectiveThirdParty contained auth_digest_maxtries +syn keyword ngxDirectiveThirdParty contained auth_digest_replays +syn keyword ngxDirectiveThirdParty contained auth_digest_shm_size +syn keyword ngxDirectiveThirdParty contained auth_digest_timeout +syn keyword ngxDirectiveThirdParty contained auth_digest_user_file + +" https://github.com/stnoonan/spnego-http-auth-nginx-module +syn keyword ngxDirectiveThirdParty contained auth_gss +syn keyword ngxDirectiveThirdParty contained auth_gss_allow_basic_fallback +syn keyword ngxDirectiveThirdParty contained auth_gss_authorized_principal +syn keyword ngxDirectiveThirdParty contained auth_gss_authorized_principal_regex +syn keyword ngxDirectiveThirdParty contained auth_gss_constrained_delegation +syn keyword ngxDirectiveThirdParty contained auth_gss_delegate_credentials +syn keyword ngxDirectiveThirdParty contained auth_gss_force_realm +syn keyword ngxDirectiveThirdParty contained auth_gss_format_full +syn keyword ngxDirectiveThirdParty contained auth_gss_keytab +syn keyword ngxDirectiveThirdParty contained auth_gss_map_to_local +syn keyword ngxDirectiveThirdParty contained auth_gss_realm +syn keyword ngxDirectiveThirdParty contained auth_gss_service_ccache +syn keyword ngxDirectiveThirdParty contained auth_gss_service_name +syn keyword ngxDirectiveThirdParty contained auth_gss_zone_name + +" https://github.com/kvspb/nginx-auth-ldap +syn keyword ngxDirectiveThirdParty contained auth_ldap +syn keyword ngxDirectiveThirdParty contained auth_ldap_cache_enabled +syn keyword ngxDirectiveThirdParty contained auth_ldap_cache_expiration_time +syn keyword ngxDirectiveThirdParty contained auth_ldap_cache_size +syn keyword ngxDirectiveThirdParty contained auth_ldap_servers +syn keyword ngxDirectiveThirdParty contained auth_ldap_servers_size +syn keyword ngxDirectiveThirdParty contained ldap_server + +" https://github.com/sto/ngx_http_auth_pam_module +syn keyword ngxDirectiveThirdParty contained auth_pam +syn keyword ngxDirectiveThirdParty contained auth_pam_service_name +syn keyword ngxDirectiveThirdParty contained auth_pam_set_pam_env + " https://github.com/arut/nginx-dav-ext-module syn keyword ngxDirectiveThirdParty contained dav_ext_lock syn keyword ngxDirectiveThirdParty contained dav_ext_lock_zone syn keyword ngxDirectiveThirdParty contained dav_ext_methods -" ngx_eval " https://github.com/openresty/nginx-eval-module syn keyword ngxDirectiveThirdParty contained eval syn keyword ngxDirectiveThirdParty contained eval_buffer_size @@ -971,9 +988,9 @@ syn keyword ngxDirectiveThirdParty contained eval_escalate syn keyword ngxDirectiveThirdParty contained eval_override_content_type syn keyword ngxDirectiveThirdParty contained eval_subrequest_in_memory -" Fancy Index " https://github.com/aperezdc/ngx-fancyindex syn keyword ngxDirectiveThirdParty contained fancyindex +syn keyword ngxDirectiveThirdParty contained fancyindex_case_sensitive syn keyword ngxDirectiveThirdParty contained fancyindex_css_href syn keyword ngxDirectiveThirdParty contained fancyindex_default_sort syn keyword ngxDirectiveThirdParty contained fancyindex_directories_first @@ -988,40 +1005,29 @@ syn keyword ngxDirectiveThirdParty contained fancyindex_show_dotfiles syn keyword ngxDirectiveThirdParty contained fancyindex_show_path syn keyword ngxDirectiveThirdParty contained fancyindex_time_format -" Footer filter " https://github.com/alibaba/nginx-http-footer-filter syn keyword ngxDirectiveThirdParty contained footer syn keyword ngxDirectiveThirdParty contained footer_types -" ngx_http_geoip2_module " https://github.com/leev/ngx_http_geoip2_module syn keyword ngxDirectiveThirdParty contained geoip2 syn keyword ngxDirectiveThirdParty contained geoip2_proxy syn keyword ngxDirectiveThirdParty contained geoip2_proxy_recursive -" A version of the Nginx HTTP stub status module that outputs in JSON format -" https://github.com/nginx-modules/nginx-json-status-module -syn keyword ngxDirectiveThirdParty contained json_status -syn keyword ngxDirectiveThirdParty contained json_status_type - -" MogileFS client for nginx -" https://github.com/vkholodkov/nginx-mogilefs-module -syn keyword ngxDirectiveThirdParty contained mogilefs_class -syn keyword ngxDirectiveThirdParty contained mogilefs_connect_timeout -syn keyword ngxDirectiveThirdParty contained mogilefs_domain -syn keyword ngxDirectiveThirdParty contained mogilefs_methods -syn keyword ngxDirectiveThirdParty contained mogilefs_noverify -syn keyword ngxDirectiveThirdParty contained mogilefs_pass -syn keyword ngxDirectiveThirdParty contained mogilefs_read_timeout -syn keyword ngxDirectiveThirdParty contained mogilefs_send_timeout -syn keyword ngxDirectiveThirdParty contained mogilefs_tracker - -" Ancient nginx plugin; probably not useful to anyone +" https://github.com/ip2location/ip2location-nginx +syn keyword ngxDirectiveThirdParty contained ip2location_database +syn keyword ngxDirectiveThirdParty contained ip2location_proxy +syn keyword ngxDirectiveThirdParty contained ip2location_proxy_recursive + +" https://github.com/ip2location/ip2proxy-nginx +syn keyword ngxDirectiveThirdParty contained ip2proxy_database +syn keyword ngxDirectiveThirdParty contained ip2proxy_proxy +syn keyword ngxDirectiveThirdParty contained ip2proxy_proxy_recursive + " https://github.com/kr/nginx-notice syn keyword ngxDirectiveThirdParty contained notice syn keyword ngxDirectiveThirdParty contained notice_type -" nchan " https://github.com/slact/nchan syn keyword ngxDirectiveThirdParty contained nchan_access_control_allow_credentials syn keyword ngxDirectiveThirdParty contained nchan_access_control_allow_origin @@ -1034,8 +1040,8 @@ syn keyword ngxDirectiveThirdParty contained nchan_benchmark_publisher_distribut syn keyword ngxDirectiveThirdParty contained nchan_benchmark_subscriber_distribution syn keyword ngxDirectiveThirdParty contained nchan_benchmark_subscribers_per_channel syn keyword ngxDirectiveThirdParty contained nchan_benchmark_time -syn keyword ngxDirectiveThirdParty contained nchan_channel_events_channel_id syn keyword ngxDirectiveThirdParty contained nchan_channel_event_string +syn keyword ngxDirectiveThirdParty contained nchan_channel_events_channel_id syn keyword ngxDirectiveThirdParty contained nchan_channel_group syn keyword ngxDirectiveThirdParty contained nchan_channel_group_accounting syn keyword ngxDirectiveThirdParty contained nchan_channel_id @@ -1073,12 +1079,32 @@ syn keyword ngxDirectiveThirdParty contained nchan_publisher_upstream_request syn keyword ngxDirectiveThirdParty contained nchan_pubsub syn keyword ngxDirectiveThirdParty contained nchan_pubsub_channel_id syn keyword ngxDirectiveThirdParty contained nchan_pubsub_location +syn keyword ngxDirectiveThirdParty contained nchan_redis_accurate_subscriber_count syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_check_interval +syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_check_interval_backoff +syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_check_interval_jitter +syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_check_interval_max +syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_check_interval_min +syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_connect_timeout +syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_max_failing_time +syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_recovery_delay +syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_recovery_delay_backoff +syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_recovery_delay_jitter +syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_recovery_delay_max +syn keyword ngxDirectiveThirdParty contained nchan_redis_cluster_recovery_delay_min +syn keyword ngxDirectiveThirdParty contained nchan_redis_command_timeout syn keyword ngxDirectiveThirdParty contained nchan_redis_connect_timeout syn keyword ngxDirectiveThirdParty contained nchan_redis_discovered_ip_range_blacklist syn keyword ngxDirectiveThirdParty contained nchan_redis_fakesub_timer_interval syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_cache_timeout +syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_keepalive_backoff +syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_keepalive_jitter +syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_keepalive_max +syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_keepalive_min +syn keyword ngxDirectiveThirdParty contained nchan_redis_idle_channel_keepalive_safety_margin +syn keyword ngxDirectiveThirdParty contained nchan_redis_load_scripts_unconditionally syn keyword ngxDirectiveThirdParty contained nchan_redis_namespace +syn keyword ngxDirectiveThirdParty contained nchan_redis_node_connect_timeout syn keyword ngxDirectiveThirdParty contained nchan_redis_nostore_fastpublish syn keyword ngxDirectiveThirdParty contained nchan_redis_optimize_target syn keyword ngxDirectiveThirdParty contained nchan_redis_pass @@ -1086,6 +1112,13 @@ syn keyword ngxDirectiveThirdParty contained nchan_redis_pass_inheritable syn keyword ngxDirectiveThirdParty contained nchan_redis_password syn keyword ngxDirectiveThirdParty contained nchan_redis_ping_interval syn keyword ngxDirectiveThirdParty contained nchan_redis_publish_msgpacked_max_size +syn keyword ngxDirectiveThirdParty contained nchan_redis_reconnect_delay +syn keyword ngxDirectiveThirdParty contained nchan_redis_reconnect_delay_backoff +syn keyword ngxDirectiveThirdParty contained nchan_redis_reconnect_delay_jitter +syn keyword ngxDirectiveThirdParty contained nchan_redis_reconnect_delay_max +syn keyword ngxDirectiveThirdParty contained nchan_redis_reconnect_delay_min +syn keyword ngxDirectiveThirdParty contained nchan_redis_retry_commands +syn keyword ngxDirectiveThirdParty contained nchan_redis_retry_commands_max_wait syn keyword ngxDirectiveThirdParty contained nchan_redis_server syn keyword ngxDirectiveThirdParty contained nchan_redis_ssl syn keyword ngxDirectiveThirdParty contained nchan_redis_ssl_ciphers @@ -1104,6 +1137,9 @@ syn keyword ngxDirectiveThirdParty contained nchan_redis_tls_server_name syn keyword ngxDirectiveThirdParty contained nchan_redis_tls_trusted_certificate syn keyword ngxDirectiveThirdParty contained nchan_redis_tls_trusted_certificate_path syn keyword ngxDirectiveThirdParty contained nchan_redis_tls_verify_certificate +syn keyword ngxDirectiveThirdParty contained nchan_redis_upstream_stats +syn keyword ngxDirectiveThirdParty contained nchan_redis_upstream_stats_disconnected_timeout +syn keyword ngxDirectiveThirdParty contained nchan_redis_upstream_stats_enabled syn keyword ngxDirectiveThirdParty contained nchan_redis_url syn keyword ngxDirectiveThirdParty contained nchan_redis_username syn keyword ngxDirectiveThirdParty contained nchan_redis_wait_after_connecting @@ -1113,10 +1149,10 @@ syn keyword ngxDirectiveThirdParty contained nchan_store_messages syn keyword ngxDirectiveThirdParty contained nchan_stub_status syn keyword ngxDirectiveThirdParty contained nchan_sub_channel_id syn keyword ngxDirectiveThirdParty contained nchan_subscribe_existing_channels_only +syn keyword ngxDirectiveThirdParty contained nchan_subscribe_request syn keyword ngxDirectiveThirdParty contained nchan_subscriber syn keyword ngxDirectiveThirdParty contained nchan_subscriber_channel_id syn keyword ngxDirectiveThirdParty contained nchan_subscriber_compound_etag_message_id -syn keyword ngxDirectiveThirdParty contained nchan_subscribe_request syn keyword ngxDirectiveThirdParty contained nchan_subscriber_first_message syn keyword ngxDirectiveThirdParty contained nchan_subscriber_http_raw_stream_separator syn keyword ngxDirectiveThirdParty contained nchan_subscriber_info @@ -1145,7 +1181,6 @@ syn keyword ngxDirectiveThirdParty contained push_subscriber syn keyword ngxDirectiveThirdParty contained push_subscriber_concurrency syn keyword ngxDirectiveThirdParty contained push_subscriber_timeout -" Push Stream " https://github.com/wandenberg/nginx-push-stream-module syn keyword ngxDirectiveThirdParty contained push_stream_allow_connections_to_events_channel syn keyword ngxDirectiveThirdParty contained push_stream_allowed_origins @@ -1184,23 +1219,6 @@ syn keyword ngxDirectiveThirdParty contained push_stream_websocket_allow_publish syn keyword ngxDirectiveThirdParty contained push_stream_wildcard_channel_max_qtd syn keyword ngxDirectiveThirdParty contained push_stream_wildcard_channel_prefix -" redis module -" https://www.nginx.com/resources/wiki/modules/redis/ -syn keyword ngxDirectiveThirdParty contained redis_bind -syn keyword ngxDirectiveThirdParty contained redis_buffer_size -syn keyword ngxDirectiveThirdParty contained redis_connect_timeout -syn keyword ngxDirectiveThirdParty contained redis_gzip_flag -syn keyword ngxDirectiveThirdParty contained redis_next_upstream -syn keyword ngxDirectiveThirdParty contained redis_pass -syn keyword ngxDirectiveThirdParty contained redis_read_timeout -syn keyword ngxDirectiveThirdParty contained redis_send_timeout - -" ngx_http_response -" http://catap.ru/downloads/nginx/ -syn keyword ngxDirectiveThirdParty contained response -syn keyword ngxDirectiveThirdParty contained response_type - -" nginx_substitutions_filter " https://github.com/yaoweibin/ngx_http_substitutions_filter_module syn keyword ngxDirectiveThirdParty contained subs_buffers syn keyword ngxDirectiveThirdParty contained subs_filter @@ -1208,7 +1226,6 @@ syn keyword ngxDirectiveThirdParty contained subs_filter_bypass syn keyword ngxDirectiveThirdParty contained subs_filter_types syn keyword ngxDirectiveThirdParty contained subs_line_buffer_size -" Tarantool nginx upstream module " https://github.com/tarantool/nginx_upstream_module syn keyword ngxDirectiveThirdParty contained tnt_allowed_indexes syn keyword ngxDirectiveThirdParty contained tnt_allowed_spaces @@ -1238,44 +1255,28 @@ syn keyword ngxDirectiveThirdParty contained tnt_set_header syn keyword ngxDirectiveThirdParty contained tnt_update syn keyword ngxDirectiveThirdParty contained tnt_upsert -" A module for nginx web server for handling file uploads using multipart/form-data encoding (RFC 1867) -" https://github.com/Austinb/nginx-upload-module +" https://github.com/fdintino/nginx-upload-module +syn keyword ngxDirectiveThirdParty contained upload_add_header syn keyword ngxDirectiveThirdParty contained upload_aggregate_form_field -syn keyword ngxDirectiveThirdParty contained upload_archive_elm -syn keyword ngxDirectiveThirdParty contained upload_archive_elm_separator -syn keyword ngxDirectiveThirdParty contained upload_archive_path -syn keyword ngxDirectiveThirdParty contained upload_archive_path_separator syn keyword ngxDirectiveThirdParty contained upload_buffer_size syn keyword ngxDirectiveThirdParty contained upload_cleanup -syn keyword ngxDirectiveThirdParty contained upload_content_type -syn keyword ngxDirectiveThirdParty contained upload_discard -syn keyword ngxDirectiveThirdParty contained upload_field_name -syn keyword ngxDirectiveThirdParty contained upload_file_crc32 -syn keyword ngxDirectiveThirdParty contained upload_file_md5 -syn keyword ngxDirectiveThirdParty contained upload_file_md5_uc -syn keyword ngxDirectiveThirdParty contained upload_file_name -syn keyword ngxDirectiveThirdParty contained upload_file_sha1 -syn keyword ngxDirectiveThirdParty contained upload_file_sha1_uc -syn keyword ngxDirectiveThirdParty contained upload_file_size -syn keyword ngxDirectiveThirdParty contained upload_filter +syn keyword ngxDirectiveThirdParty contained upload_empty_fiels_names +syn keyword ngxDirectiveThirdParty contained upload_limit_rate syn keyword ngxDirectiveThirdParty contained upload_max_file_size syn keyword ngxDirectiveThirdParty contained upload_max_output_body_len syn keyword ngxDirectiveThirdParty contained upload_max_part_header_len +syn keyword ngxDirectiveThirdParty contained upload_merge_buffer_size syn keyword ngxDirectiveThirdParty contained upload_pass syn keyword ngxDirectiveThirdParty contained upload_pass_args syn keyword ngxDirectiveThirdParty contained upload_pass_form_field +syn keyword ngxDirectiveThirdParty contained upload_range_header_buffer_size +syn keyword ngxDirectiveThirdParty contained upload_resumable syn keyword ngxDirectiveThirdParty contained upload_set_form_field +syn keyword ngxDirectiveThirdParty contained upload_state_store syn keyword ngxDirectiveThirdParty contained upload_store syn keyword ngxDirectiveThirdParty contained upload_store_access -syn keyword ngxDirectiveThirdParty contained upload_tmp_path -syn keyword ngxDirectiveThirdParty contained upload_unzip -syn keyword ngxDirectiveThirdParty contained upload_unzip_buffers -syn keyword ngxDirectiveThirdParty contained upload_unzip_hash -syn keyword ngxDirectiveThirdParty contained upload_unzip_max_file_name_len -syn keyword ngxDirectiveThirdParty contained upload_unzip_window -syn keyword ngxDirectiveThirdParty contained upload_void_content_type - -" nginx-upload-progress-module +syn keyword ngxDirectiveThirdParty contained upload_tame_arrays + " https://github.com/masterzen/nginx-upload-progress-module syn keyword ngxDirectiveThirdParty contained report_uploads syn keyword ngxDirectiveThirdParty contained track_uploads @@ -1288,7 +1289,6 @@ syn keyword ngxDirectiveThirdParty contained upload_progress_jsonp_output syn keyword ngxDirectiveThirdParty contained upload_progress_jsonp_parameter syn keyword ngxDirectiveThirdParty contained upload_progress_template -" Health checks upstreams for nginx " https://github.com/yaoweibin/nginx_upstream_check_module syn keyword ngxDirectiveThirdParty contained check syn keyword ngxDirectiveThirdParty contained check_fastcgi_param @@ -1298,13 +1298,15 @@ syn keyword ngxDirectiveThirdParty contained check_keepalive_requests syn keyword ngxDirectiveThirdParty contained check_shm_size syn keyword ngxDirectiveThirdParty contained check_status -" The fair load balancer module for nginx -" https://github.com/cryptofuture/nginx-upstream-fair +" https://github.com/jaygooby/nginx-upstream-fair syn keyword ngxDirectiveThirdParty contained fair syn keyword ngxDirectiveThirdParty contained upstream_fair_shm_size -" Nginx Video Thumb Extractor Module -" https://github.com/wandenberg/nginx-video-thumbextractor-module +" https://github.com/ayty-adrianomartins/nginx-sticky-module-ng +syn keyword ngxDirectiveThirdParty contained sticky_hide_cookie +syn keyword ngxDirectiveThirdParty contained sticky_no_fallback + +" https://github.com/Novetta/nginx-video-thumbextractor-module syn keyword ngxDirectiveThirdParty contained video_thumbextractor syn keyword ngxDirectiveThirdParty contained video_thumbextractor_image_height syn keyword ngxDirectiveThirdParty contained video_thumbextractor_image_width @@ -1329,43 +1331,14 @@ syn keyword ngxDirectiveThirdParty contained video_thumbextractor_tile_sample_in syn keyword ngxDirectiveThirdParty contained video_thumbextractor_video_filename syn keyword ngxDirectiveThirdParty contained video_thumbextractor_video_second -" drizzle-nginx-module - Upstream module for talking to MySQL and Drizzle directly -" https://github.com/openresty/drizzle-nginx-module -syn keyword ngxDirectiveThirdParty contained drizzle_buffer_size -syn keyword ngxDirectiveThirdParty contained drizzle_connect_timeout -syn keyword ngxDirectiveThirdParty contained drizzle_dbname -syn keyword ngxDirectiveThirdParty contained drizzle_keepalive -syn keyword ngxDirectiveThirdParty contained drizzle_module_header -syn keyword ngxDirectiveThirdParty contained drizzle_pass -syn keyword ngxDirectiveThirdParty contained drizzle_query -syn keyword ngxDirectiveThirdParty contained drizzle_recv_cols_timeout -syn keyword ngxDirectiveThirdParty contained drizzle_recv_rows_timeout -syn keyword ngxDirectiveThirdParty contained drizzle_send_query_timeout -syn keyword ngxDirectiveThirdParty contained drizzle_server -syn keyword ngxDirectiveThirdParty contained drizzle_status - -" ngx_dynamic_upstream -" https://github.com/cubicdaiya/ngx_dynamic_upstream -syn keyword ngxDirectiveThirdParty contained dynamic_upstream - -" encrypt and decrypt nginx variable values -" https://github.com/openresty/encrypted-session-nginx-module -syn keyword ngxDirectiveThirdParty contained encrypted_session_expires -syn keyword ngxDirectiveThirdParty contained encrypted_session_iv -syn keyword ngxDirectiveThirdParty contained encrypted_session_key -syn keyword ngxDirectiveThirdParty contained set_decrypt_session -syn keyword ngxDirectiveThirdParty contained set_encrypt_session - -" serve content directly from MongoDB's GridFS -" https://github.com/mdirolf/nginx-gridfs -syn keyword ngxDirectiveThirdParty contained gridfs -syn keyword ngxDirectiveThirdParty contained mongo +" https://github.com/calio/iconv-nginx-module +syn keyword ngxDirectiveThirdParty contained iconv_buffer_size +syn keyword ngxDirectiveThirdParty contained iconv_filter +syn keyword ngxDirectiveThirdParty contained set_iconv -" Adds support for arithmetic operations to NGINX config -" https://github.com/arut/nginx-let-module +" https://github.com/baysao/nginx-let-module syn keyword ngxDirectiveThirdParty contained let -" ngx_http_lua_module - Embed the power of Lua into Nginx HTTP Servers " https://github.com/openresty/lua-nginx-module syn keyword ngxDirectiveThirdParty contained access_by_lua syn keyword ngxDirectiveThirdParty contained access_by_lua_block @@ -1417,6 +1390,8 @@ syn keyword ngxDirectiveThirdParty contained lua_socket_pool_size syn keyword ngxDirectiveThirdParty contained lua_socket_read_timeout syn keyword ngxDirectiveThirdParty contained lua_socket_send_lowat syn keyword ngxDirectiveThirdParty contained lua_socket_send_timeout +syn keyword ngxDirectiveThirdParty contained lua_ssl_certificate +syn keyword ngxDirectiveThirdParty contained lua_ssl_certificate_key syn keyword ngxDirectiveThirdParty contained lua_ssl_ciphers syn keyword ngxDirectiveThirdParty contained lua_ssl_conf_command syn keyword ngxDirectiveThirdParty contained lua_ssl_crl @@ -1431,6 +1406,8 @@ syn keyword ngxDirectiveThirdParty contained rewrite_by_lua syn keyword ngxDirectiveThirdParty contained rewrite_by_lua_block syn keyword ngxDirectiveThirdParty contained rewrite_by_lua_file syn keyword ngxDirectiveThirdParty contained rewrite_by_lua_no_postpone +syn keyword ngxDirectiveThirdParty contained server_rewrite_by_lua_block +syn keyword ngxDirectiveThirdParty contained server_rewrite_by_lua_file syn keyword ngxDirectiveThirdParty contained set_by_lua syn keyword ngxDirectiveThirdParty contained set_by_lua_block syn keyword ngxDirectiveThirdParty contained set_by_lua_file @@ -1443,7 +1420,16 @@ syn keyword ngxDirectiveThirdParty contained ssl_session_fetch_by_lua_file syn keyword ngxDirectiveThirdParty contained ssl_session_store_by_lua_block syn keyword ngxDirectiveThirdParty contained ssl_session_store_by_lua_file -" ngx_memc - An extended version of the standard memcached module +" https://github.com/Taymindis/nginx-link-function +syn keyword ngxDirectiveThirdParty contained ngx_link_func_add_prop +syn keyword ngxDirectiveThirdParty contained ngx_link_func_add_req_header +syn keyword ngxDirectiveThirdParty contained ngx_link_func_ca_cert +syn keyword ngxDirectiveThirdParty contained ngx_link_func_call +syn keyword ngxDirectiveThirdParty contained ngx_link_func_download_link_lib +syn keyword ngxDirectiveThirdParty contained ngx_link_func_lib +syn keyword ngxDirectiveThirdParty contained ngx_link_func_shm_size +syn keyword ngxDirectiveThirdParty contained ngx_link_func_subrequest + " https://github.com/openresty/memc-nginx-module syn keyword ngxDirectiveThirdParty contained memc_buffer_size syn keyword ngxDirectiveThirdParty contained memc_cmds_allowed @@ -1457,21 +1443,24 @@ syn keyword ngxDirectiveThirdParty contained memc_send_timeout syn keyword ngxDirectiveThirdParty contained memc_upstream_fail_timeout syn keyword ngxDirectiveThirdParty contained memc_upstream_max_fails -" ModSecurity web application firewall -" https://github.com/SpiderLabs/ModSecurity/tree/master -syn keyword ngxDirectiveThirdParty contained ModSecurityConfig -syn keyword ngxDirectiveThirdParty contained ModSecurityEnabled -syn keyword ngxDirectiveThirdParty contained pool_context_hash_size +" https://github.com/SpiderLabs/ModSecurity-nginx +syn keyword ngxDirectiveThirdParty contained modsecurity +syn keyword ngxDirectiveThirdParty contained modsecurity_rules +syn keyword ngxDirectiveThirdParty contained modsecurity_rules_file +syn keyword ngxDirectiveThirdParty contained modsecurity_rules_remote +syn keyword ngxDirectiveThirdParty contained modsecurity_transaction_id -" NAXSI is an open-source, high performance, low rules maintenance WAF for NGINX " https://github.com/nbs-system/naxsi syn keyword ngxDirectiveThirdParty contained BasicRule syn keyword ngxDirectiveThirdParty contained CheckRule syn keyword ngxDirectiveThirdParty contained DeniedUrl +syn keyword ngxDirectiveThirdParty contained IgnoreCIDR +syn keyword ngxDirectiveThirdParty contained IgnoreIP syn keyword ngxDirectiveThirdParty contained LearningMode syn keyword ngxDirectiveThirdParty contained LibInjectionSql syn keyword ngxDirectiveThirdParty contained LibInjectionXss syn keyword ngxDirectiveThirdParty contained MainRule +syn keyword ngxDirectiveThirdParty contained NaxsiLogFile syn keyword ngxDirectiveThirdParty contained SecRulesDisabled syn keyword ngxDirectiveThirdParty contained SecRulesEnabled syn keyword ngxDirectiveThirdParty contained basic_rule @@ -1481,17 +1470,31 @@ syn keyword ngxDirectiveThirdParty contained learning_mode syn keyword ngxDirectiveThirdParty contained libinjection_sql syn keyword ngxDirectiveThirdParty contained libinjection_xss syn keyword ngxDirectiveThirdParty contained main_rule +syn keyword ngxDirectiveThirdParty contained naxsi_log syn keyword ngxDirectiveThirdParty contained rules_disabled syn keyword ngxDirectiveThirdParty contained rules_enabled -" Phusion Passenger -" https://www.phusionpassenger.com/library/config/nginx/reference/ +" https://github.com/opentracing-contrib/nginx-opentracing +syn keyword ngxDirectiveThirdParty contained opentracing +syn keyword ngxDirectiveThirdParty contained opentracing_fastcgi_propagate_context +syn keyword ngxDirectiveThirdParty contained opentracing_grpc_propagate_context +syn keyword ngxDirectiveThirdParty contained opentracing_load_tracer +syn keyword ngxDirectiveThirdParty contained opentracing_location_operation_name +syn keyword ngxDirectiveThirdParty contained opentracing_operation_name +syn keyword ngxDirectiveThirdParty contained opentracing_propagate_context +syn keyword ngxDirectiveThirdParty contained opentracing_tag +syn keyword ngxDirectiveThirdParty contained opentracing_trace_locations +syn keyword ngxDirectiveThirdParty contained opentracing_trust_incoming_span + +" https://github.com/phusion/passenger syn keyword ngxDirectiveThirdParty contained passenger_abort_on_startup_error syn keyword ngxDirectiveThirdParty contained passenger_abort_websockets_on_process_shutdown syn keyword ngxDirectiveThirdParty contained passenger_admin_panel_auth_type syn keyword ngxDirectiveThirdParty contained passenger_admin_panel_password syn keyword ngxDirectiveThirdParty contained passenger_admin_panel_url syn keyword ngxDirectiveThirdParty contained passenger_admin_panel_username +syn keyword ngxDirectiveThirdParty contained passenger_analytics_log_group +syn keyword ngxDirectiveThirdParty contained passenger_analytics_log_user syn keyword ngxDirectiveThirdParty contained passenger_anonymous_telemetry_proxy syn keyword ngxDirectiveThirdParty contained passenger_app_env syn keyword ngxDirectiveThirdParty contained passenger_app_file_descriptor_ulimit @@ -1499,20 +1502,25 @@ syn keyword ngxDirectiveThirdParty contained passenger_app_group_name syn keyword ngxDirectiveThirdParty contained passenger_app_log_file syn keyword ngxDirectiveThirdParty contained passenger_app_rights syn keyword ngxDirectiveThirdParty contained passenger_app_root +syn keyword ngxDirectiveThirdParty contained passenger_app_start_command syn keyword ngxDirectiveThirdParty contained passenger_app_type syn keyword ngxDirectiveThirdParty contained passenger_base_uri syn keyword ngxDirectiveThirdParty contained passenger_buffer_response syn keyword ngxDirectiveThirdParty contained passenger_buffer_size +syn keyword ngxDirectiveThirdParty contained passenger_buffer_upload syn keyword ngxDirectiveThirdParty contained passenger_buffers syn keyword ngxDirectiveThirdParty contained passenger_busy_buffers_size syn keyword ngxDirectiveThirdParty contained passenger_concurrency_model syn keyword ngxDirectiveThirdParty contained passenger_core_file_descriptor_ulimit syn keyword ngxDirectiveThirdParty contained passenger_ctl syn keyword ngxDirectiveThirdParty contained passenger_data_buffer_dir +syn keyword ngxDirectiveThirdParty contained passenger_debug_log_file syn keyword ngxDirectiveThirdParty contained passenger_debugger syn keyword ngxDirectiveThirdParty contained passenger_default_group syn keyword ngxDirectiveThirdParty contained passenger_default_user +syn keyword ngxDirectiveThirdParty contained passenger_direct_instance_request_address syn keyword ngxDirectiveThirdParty contained passenger_disable_anonymous_telemetry +syn keyword ngxDirectiveThirdParty contained passenger_disable_log_prefix syn keyword ngxDirectiveThirdParty contained passenger_disable_security_update_check syn keyword ngxDirectiveThirdParty contained passenger_document_root syn keyword ngxDirectiveThirdParty contained passenger_dump_config_manifest @@ -1548,8 +1556,10 @@ syn keyword ngxDirectiveThirdParty contained passenger_nodejs syn keyword ngxDirectiveThirdParty contained passenger_pass_header syn keyword ngxDirectiveThirdParty contained passenger_pool_idle_time syn keyword ngxDirectiveThirdParty contained passenger_pre_start +syn keyword ngxDirectiveThirdParty contained passenger_preload_bundler syn keyword ngxDirectiveThirdParty contained passenger_python syn keyword ngxDirectiveThirdParty contained passenger_read_timeout +syn keyword ngxDirectiveThirdParty contained passenger_request_buffering syn keyword ngxDirectiveThirdParty contained passenger_request_queue_overflow_status_code syn keyword ngxDirectiveThirdParty contained passenger_resist_deployment_errors syn keyword ngxDirectiveThirdParty contained passenger_response_buffer_high_watermark @@ -1561,36 +1571,36 @@ syn keyword ngxDirectiveThirdParty contained passenger_security_update_check_pro syn keyword ngxDirectiveThirdParty contained passenger_set_header syn keyword ngxDirectiveThirdParty contained passenger_show_version_in_header syn keyword ngxDirectiveThirdParty contained passenger_socket_backlog +syn keyword ngxDirectiveThirdParty contained passenger_spawn_dir +syn keyword ngxDirectiveThirdParty contained passenger_spawn_exception_status_code syn keyword ngxDirectiveThirdParty contained passenger_spawn_method syn keyword ngxDirectiveThirdParty contained passenger_start_timeout syn keyword ngxDirectiveThirdParty contained passenger_startup_file syn keyword ngxDirectiveThirdParty contained passenger_stat_throttle_rate syn keyword ngxDirectiveThirdParty contained passenger_sticky_sessions +syn keyword ngxDirectiveThirdParty contained passenger_sticky_sessions_cookie_attributes syn keyword ngxDirectiveThirdParty contained passenger_sticky_sessions_cookie_name +syn keyword ngxDirectiveThirdParty contained passenger_temp_path syn keyword ngxDirectiveThirdParty contained passenger_thread_count syn keyword ngxDirectiveThirdParty contained passenger_turbocaching +syn keyword ngxDirectiveThirdParty contained passenger_use_global_queue syn keyword ngxDirectiveThirdParty contained passenger_user syn keyword ngxDirectiveThirdParty contained passenger_user_switching syn keyword ngxDirectiveThirdParty contained passenger_vary_turbocache_by_cookie -syn keyword ngxDirectiveThirdPartyDeprecated contained passenger_analytics_log_group -syn keyword ngxDirectiveThirdPartyDeprecated contained passenger_analytics_log_user -syn keyword ngxDirectiveThirdPartyDeprecated contained passenger_debug_log_file -syn keyword ngxDirectiveThirdPartyDeprecated contained passenger_use_global_queue -syn keyword ngxDirectiveThirdPartyDeprecated contained rack_env -syn keyword ngxDirectiveThirdPartyDeprecated contained rails_app_spawner_idle_time -syn keyword ngxDirectiveThirdPartyDeprecated contained rails_env -syn keyword ngxDirectiveThirdPartyDeprecated contained rails_framework_spawner_idle_time -syn keyword ngxDirectiveThirdPartyDeprecated contained rails_spawn_method -syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_filter -syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_gateway_address -syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_gateway_cert -syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_gateway_port -syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_key -syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_proxy_address -syn keyword ngxDirectiveThirdPartyDeprecated contained union_station_support - -" ngx_postgres is an upstream module that allows nginx to communicate directly with PostgreSQL database -" https://github.com/FRiCKLE/ngx_postgres +syn keyword ngxDirectiveThirdParty contained rack_env +syn keyword ngxDirectiveThirdParty contained rails_app_spawner_idle_time +syn keyword ngxDirectiveThirdParty contained rails_env +syn keyword ngxDirectiveThirdParty contained rails_framework_spawner_idle_time +syn keyword ngxDirectiveThirdParty contained rails_spawn_method +syn keyword ngxDirectiveThirdParty contained union_station_filter +syn keyword ngxDirectiveThirdParty contained union_station_gateway_address +syn keyword ngxDirectiveThirdParty contained union_station_gateway_cert +syn keyword ngxDirectiveThirdParty contained union_station_gateway_port +syn keyword ngxDirectiveThirdParty contained union_station_key +syn keyword ngxDirectiveThirdParty contained union_station_proxy_address +syn keyword ngxDirectiveThirdParty contained union_station_support + +" https://github.com/konstruxi/ngx_postgres syn keyword ngxDirectiveThirdParty contained postgres_connect_timeout syn keyword ngxDirectiveThirdParty contained postgres_escape syn keyword ngxDirectiveThirdParty contained postgres_keepalive @@ -1602,7 +1612,6 @@ syn keyword ngxDirectiveThirdParty contained postgres_rewrite syn keyword ngxDirectiveThirdParty contained postgres_server syn keyword ngxDirectiveThirdParty contained postgres_set -" ngx_rds_csv - Nginx output filter module to convert Resty-DBD-Streams (RDS) to Comma-Separated Values (CSV) " https://github.com/openresty/rds-csv-nginx-module syn keyword ngxDirectiveThirdParty contained rds_csv syn keyword ngxDirectiveThirdParty contained rds_csv_buffer_size @@ -1611,7 +1620,6 @@ syn keyword ngxDirectiveThirdParty contained rds_csv_field_name_header syn keyword ngxDirectiveThirdParty contained rds_csv_field_separator syn keyword ngxDirectiveThirdParty contained rds_csv_row_terminator -" ngx_rds_json - an output filter that formats Resty DBD Streams generated by ngx_drizzle and others to JSON " https://github.com/openresty/rds-json-nginx-module syn keyword ngxDirectiveThirdParty contained rds_json syn keyword ngxDirectiveThirdParty contained rds_json_buffer_size @@ -1624,7 +1632,6 @@ syn keyword ngxDirectiveThirdParty contained rds_json_root syn keyword ngxDirectiveThirdParty contained rds_json_success_property syn keyword ngxDirectiveThirdParty contained rds_json_user_property -" ngx_redis2 - Nginx upstream module for the Redis 2.0 protocol " https://github.com/openresty/redis2-nginx-module syn keyword ngxDirectiveThirdParty contained redis2_bind syn keyword ngxDirectiveThirdParty contained redis2_buffer_size @@ -1638,7 +1645,6 @@ syn keyword ngxDirectiveThirdParty contained redis2_raw_query syn keyword ngxDirectiveThirdParty contained redis2_read_timeout syn keyword ngxDirectiveThirdParty contained redis2_send_timeout -" NGINX-based Media Streaming Server " https://github.com/arut/nginx-rtmp-module syn keyword ngxDirectiveThirdParty contained ack_window syn keyword ngxDirectiveThirdParty contained application @@ -1750,7 +1756,6 @@ syn keyword ngxDirectiveThirdParty contained sync syn keyword ngxDirectiveThirdParty contained wait_key syn keyword ngxDirectiveThirdParty contained wait_video -" ngx_set_misc - Various set_xxx directives added to nginx's rewrite module (md5/sha1, sql/json quoting, and many more) " https://github.com/openresty/set-misc-nginx-module syn keyword ngxDirectiveThirdParty contained set_base32_alphabet syn keyword ngxDirectiveThirdParty contained set_base32_padding @@ -1770,6 +1775,7 @@ syn keyword ngxDirectiveThirdParty contained set_hmac_sha1 syn keyword ngxDirectiveThirdParty contained set_hmac_sha256 syn keyword ngxDirectiveThirdParty contained set_if_empty syn keyword ngxDirectiveThirdParty contained set_local_today +syn keyword ngxDirectiveThirdParty contained set_md5 syn keyword ngxDirectiveThirdParty contained set_misc_base32_padding syn keyword ngxDirectiveThirdParty contained set_quote_json_str syn keyword ngxDirectiveThirdParty contained set_quote_pgsql_str @@ -1778,20 +1784,18 @@ syn keyword ngxDirectiveThirdParty contained set_random syn keyword ngxDirectiveThirdParty contained set_rotate syn keyword ngxDirectiveThirdParty contained set_secure_random_alphanum syn keyword ngxDirectiveThirdParty contained set_secure_random_lcalpha +syn keyword ngxDirectiveThirdParty contained set_sha1 syn keyword ngxDirectiveThirdParty contained set_unescape_uri -" nginx-sflow-module " https://github.com/sflow/nginx-sflow-module syn keyword ngxDirectiveThirdParty contained sflow -" Shibboleth auth request module for Nginx " https://github.com/nginx-shib/nginx-http-shibboleth syn keyword ngxDirectiveThirdParty contained shib_request syn keyword ngxDirectiveThirdParty contained shib_request_set syn keyword ngxDirectiveThirdParty contained shib_request_use_headers -" nginx module which adds ability to cache static files -" https://github.com/FRiCKLE/ngx_slowfs_cache +" https://github.com/baysao/ngx_slowfs_cache syn keyword ngxDirectiveThirdParty contained slowfs_big_file_size syn keyword ngxDirectiveThirdParty contained slowfs_cache syn keyword ngxDirectiveThirdParty contained slowfs_cache_key @@ -1801,18 +1805,6 @@ syn keyword ngxDirectiveThirdParty contained slowfs_cache_purge syn keyword ngxDirectiveThirdParty contained slowfs_cache_valid syn keyword ngxDirectiveThirdParty contained slowfs_temp_path -" Dynamic Image Transformation Module For nginx -" https://github.com/cubicdaiya/ngx_small_light -syn keyword ngxDirectiveThirdParty contained small_light -syn keyword ngxDirectiveThirdParty contained small_light_buffer -syn keyword ngxDirectiveThirdParty contained small_light_getparam_mode -syn keyword ngxDirectiveThirdParty contained small_light_imlib2_temp_dir -syn keyword ngxDirectiveThirdParty contained small_light_material_dir -syn keyword ngxDirectiveThirdParty contained small_light_pattern_define -syn keyword ngxDirectiveThirdParty contained small_light_radius_max -syn keyword ngxDirectiveThirdParty contained small_light_sigma_max - -" ngx_srcache - Transparent subrequest-based caching layout for arbitrary nginx locations " https://github.com/openresty/srcache-nginx-module syn keyword ngxDirectiveThirdParty contained srcache_buffer syn keyword ngxDirectiveThirdParty contained srcache_default_expire @@ -1835,7 +1827,6 @@ syn keyword ngxDirectiveThirdParty contained srcache_store_ranges syn keyword ngxDirectiveThirdParty contained srcache_store_skip syn keyword ngxDirectiveThirdParty contained srcache_store_statuses -" NGINX-based VOD Packager " https://github.com/kaltura/nginx-vod-module syn keyword ngxDirectiveThirdParty contained vod syn keyword ngxDirectiveThirdParty contained vod_align_segments_to_key_frames @@ -1875,6 +1866,7 @@ syn keyword ngxDirectiveThirdParty contained vod_live_window_duration syn keyword ngxDirectiveThirdParty contained vod_manifest_duration_policy syn keyword ngxDirectiveThirdParty contained vod_manifest_segment_durations_mode syn keyword ngxDirectiveThirdParty contained vod_mapping_cache +syn keyword ngxDirectiveThirdParty contained vod_max_frame_count syn keyword ngxDirectiveThirdParty contained vod_max_frames_size syn keyword ngxDirectiveThirdParty contained vod_max_mapping_response_size syn keyword ngxDirectiveThirdParty contained vod_max_metadata_size @@ -1901,6 +1893,7 @@ syn keyword ngxDirectiveThirdParty contained vod_response_cache syn keyword ngxDirectiveThirdParty contained vod_secret_key syn keyword ngxDirectiveThirdParty contained vod_segment_count_policy syn keyword ngxDirectiveThirdParty contained vod_segment_duration +syn keyword ngxDirectiveThirdParty contained vod_segment_max_frame_count syn keyword ngxDirectiveThirdParty contained vod_segments_base_url syn keyword ngxDirectiveThirdParty contained vod_source_clip_map_uri syn keyword ngxDirectiveThirdParty contained vod_speed_param_name @@ -1910,7 +1903,6 @@ syn keyword ngxDirectiveThirdParty contained vod_tracks_param_name syn keyword ngxDirectiveThirdParty contained vod_upstream_extra_args syn keyword ngxDirectiveThirdParty contained vod_upstream_location -" Nginx virtual host traffic status module " https://github.com/vozlt/nginx-module-vts syn keyword ngxDirectiveThirdParty contained vhost_traffic_status syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_average_method @@ -1934,7 +1926,6 @@ syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_limit_traffic_ syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_set_by_filter syn keyword ngxDirectiveThirdParty contained vhost_traffic_status_zone -" xss-nginx-module - Native cross-site scripting support in nginx " https://github.com/openresty/xss-nginx-module syn keyword ngxDirectiveThirdParty contained xss_callback_arg syn keyword ngxDirectiveThirdParty contained xss_check_status @@ -1943,471 +1934,6 @@ syn keyword ngxDirectiveThirdParty contained xss_input_types syn keyword ngxDirectiveThirdParty contained xss_output_type syn keyword ngxDirectiveThirdParty contained xss_override_status -" Add support for array-typed variables to nginx config files -" https://github.com/openresty/array-var-nginx-module -syn keyword ngxDirectiveThirdParty contained array_join -syn keyword ngxDirectiveThirdParty contained array_map -syn keyword ngxDirectiveThirdParty contained array_map_op -syn keyword ngxDirectiveThirdParty contained array_split - -" NGINX module for Brotli compression -" https://github.com/eustas/ngx_brotli -syn keyword ngxDirectiveThirdParty contained brotli -syn keyword ngxDirectiveThirdParty contained brotli_buffers -syn keyword ngxDirectiveThirdParty contained brotli_comp_level -syn keyword ngxDirectiveThirdParty contained brotli_min_length -syn keyword ngxDirectiveThirdParty contained brotli_static -syn keyword ngxDirectiveThirdParty contained brotli_types -syn keyword ngxDirectiveThirdParty contained brotli_window - -" form-input-nginx-module -" https://github.com/calio/form-input-nginx-module -syn keyword ngxDirectiveThirdParty contained set_form_input -syn keyword ngxDirectiveThirdParty contained set_form_input_multi - -" character conversion nginx module using libiconv -" https://github.com/calio/iconv-nginx-module -syn keyword ngxDirectiveThirdParty contained iconv_buffer_size -syn keyword ngxDirectiveThirdParty contained iconv_filter -syn keyword ngxDirectiveThirdParty contained set_iconv - -" 3rd party modules list taken from -" https://www.nginx.com/resources/wiki/modules/ -" --------------------------------------------- - -" Nginx Module for Authenticating Akamai G2O requests -" https://github.com/kaltura/nginx_mod_akamai_g2o -syn keyword ngxDirectiveThirdParty contained g2o -syn keyword ngxDirectiveThirdParty contained g2o_data_header -syn keyword ngxDirectiveThirdParty contained g2o_hash_function -syn keyword ngxDirectiveThirdParty contained g2o_key -syn keyword ngxDirectiveThirdParty contained g2o_log_level -syn keyword ngxDirectiveThirdParty contained g2o_nonce -syn keyword ngxDirectiveThirdParty contained g2o_sign_header -syn keyword ngxDirectiveThirdParty contained g2o_time_window -syn keyword ngxDirectiveThirdParty contained g2o_version - -" nginx_lua_module -" https://github.com/alacner/nginx_lua_module -syn keyword ngxDirectiveThirdParty contained lua_file - -" Nginx Audio Track for HTTP Live Streaming -" https://github.com/flavioribeiro/nginx-audio-track-for-hls-module -syn keyword ngxDirectiveThirdParty contained ngx_hls_audio_track -syn keyword ngxDirectiveThirdParty contained ngx_hls_audio_track_output_format -syn keyword ngxDirectiveThirdParty contained ngx_hls_audio_track_output_header -syn keyword ngxDirectiveThirdParty contained ngx_hls_audio_track_rootpath - -" A Nginx module to dump backtrace when a worker process exits abnormally -" https://github.com/alibaba/nginx-backtrace -syn keyword ngxDirectiveThirdParty contained backtrace_log -syn keyword ngxDirectiveThirdParty contained backtrace_max_stack_size - -" circle_gif module -" https://github.com/evanmiller/nginx_circle_gif -syn keyword ngxDirectiveThirdParty contained circle_gif -syn keyword ngxDirectiveThirdParty contained circle_gif_max_radius -syn keyword ngxDirectiveThirdParty contained circle_gif_min_radius -syn keyword ngxDirectiveThirdParty contained circle_gif_step_radius - -" Upstream Consistent Hash -" https://github.com/replay/ngx_http_consistent_hash -syn keyword ngxDirectiveThirdParty contained consistent_hash - -" Nginx module for etags on dynamic content -" https://github.com/kali/nginx-dynamic-etags -syn keyword ngxDirectiveThirdParty contained dynamic_etags - -" Enhanced Nginx Memcached Module -" https://github.com/bpaquet/ngx_http_enhanced_memcached_module -syn keyword ngxDirectiveThirdParty contained enhanced_memcached_allow_delete -syn keyword ngxDirectiveThirdParty contained enhanced_memcached_allow_put -syn keyword ngxDirectiveThirdParty contained enhanced_memcached_bind -syn keyword ngxDirectiveThirdParty contained enhanced_memcached_buffer_size -syn keyword ngxDirectiveThirdParty contained enhanced_memcached_connect_timeout -syn keyword ngxDirectiveThirdParty contained enhanced_memcached_flush -syn keyword ngxDirectiveThirdParty contained enhanced_memcached_flush_namespace -syn keyword ngxDirectiveThirdParty contained enhanced_memcached_hash_keys_with_md5 -syn keyword ngxDirectiveThirdParty contained enhanced_memcached_pass -syn keyword ngxDirectiveThirdParty contained enhanced_memcached_read_timeout -syn keyword ngxDirectiveThirdParty contained enhanced_memcached_send_timeout -syn keyword ngxDirectiveThirdParty contained enhanced_memcached_stats - -" nginx max connections queue -" https://github.com/ezmobius/nginx-ey-balancer -syn keyword ngxDirectiveThirdParty contained max_connections_max_queue_length -syn keyword ngxDirectiveThirdParty contained max_connections_queue_timeout - -" Nginx module for POST authentication and authorization -" https://github.com/veruu/ngx_form_auth -syn keyword ngxDirectiveThirdParty contained form_auth -syn keyword ngxDirectiveThirdParty contained form_auth_login -syn keyword ngxDirectiveThirdParty contained form_auth_pam_service -syn keyword ngxDirectiveThirdParty contained form_auth_password -syn keyword ngxDirectiveThirdParty contained form_auth_remote_user - -" ngx_http_accounting_module -" https://github.com/Lax/ngx_http_accounting_module -syn keyword ngxDirectiveThirdParty contained accounting -syn keyword ngxDirectiveThirdParty contained accounting_id -syn keyword ngxDirectiveThirdParty contained accounting_interval -syn keyword ngxDirectiveThirdParty contained accounting_log -syn keyword ngxDirectiveThirdParty contained accounting_perturb - -" concatenating files in a given context: CSS and JS files usually -" https://github.com/alibaba/nginx-http-concat -syn keyword ngxDirectiveThirdParty contained concat -syn keyword ngxDirectiveThirdParty contained concat_delimiter -syn keyword ngxDirectiveThirdParty contained concat_ignore_file_error -syn keyword ngxDirectiveThirdParty contained concat_max_files -syn keyword ngxDirectiveThirdParty contained concat_types -syn keyword ngxDirectiveThirdParty contained concat_unique - -" update upstreams' config by restful interface -" https://github.com/yzprofile/ngx_http_dyups_module -syn keyword ngxDirectiveThirdParty contained dyups_interface -syn keyword ngxDirectiveThirdParty contained dyups_shm_zone_size - -" add given content to the end of the response according to the condition specified -" https://github.com/flygoast/ngx_http_footer_if_filter -syn keyword ngxDirectiveThirdParty contained footer_if - -" NGINX HTTP Internal Redirect Module -" https://github.com/flygoast/ngx_http_internal_redirect -syn keyword ngxDirectiveThirdParty contained internal_redirect_if -syn keyword ngxDirectiveThirdParty contained internal_redirect_if_no_postpone - -" nginx-ip-blocker -" https://github.com/tmthrgd/nginx-ip-blocker -syn keyword ngxDirectiveThirdParty contained ip_blocker - -" IP2Location Nginx -" https://github.com/chrislim2888/ip2location-nginx -syn keyword ngxDirectiveThirdParty contained ip2location_database - -" Limit upload rate -" https://github.com/cfsego/limit_upload_rate -syn keyword ngxDirectiveThirdParty contained limit_upload_rate -syn keyword ngxDirectiveThirdParty contained limit_upload_rate_after -syn keyword ngxDirectiveThirdParty contained limit_upload_rate_log_level - -" limit the number of connections to upstream -" https://github.com/cfsego/nginx-limit-upstream -syn keyword ngxDirectiveThirdParty contained limit_upstream_conn -syn keyword ngxDirectiveThirdParty contained limit_upstream_log_level -syn keyword ngxDirectiveThirdParty contained limit_upstream_zone - -" conditional accesslog for nginx -" https://github.com/cfsego/ngx_log_if -syn keyword ngxDirectiveThirdParty contained access_log_bypass_if - -" log messages over ZeroMQ -" https://github.com/alticelabs/nginx-log-zmq -syn keyword ngxDirectiveThirdParty contained log_zmq_endpoint -syn keyword ngxDirectiveThirdParty contained log_zmq_format -syn keyword ngxDirectiveThirdParty contained log_zmq_off -syn keyword ngxDirectiveThirdParty contained log_zmq_server - -" simple module to uppercase/lowercase strings in the nginx config -" https://github.com/replay/ngx_http_lower_upper_case -syn keyword ngxDirectiveThirdParty contained lower -syn keyword ngxDirectiveThirdParty contained upper - -" content filter for nginx, which returns the md5 hash of the content otherwise returned -" https://github.com/kainswor/nginx_md5_filter -syn keyword ngxDirectiveThirdParty contained md5_filter - -" Non-blocking upstream module for Nginx to connect to MongoDB -" https://github.com/simpl/ngx_mongo -syn keyword ngxDirectiveThirdParty contained mongo_auth -syn keyword ngxDirectiveThirdParty contained mongo_bind -syn keyword ngxDirectiveThirdParty contained mongo_buffer_size -syn keyword ngxDirectiveThirdParty contained mongo_buffering -syn keyword ngxDirectiveThirdParty contained mongo_buffers -syn keyword ngxDirectiveThirdParty contained mongo_busy_buffers_size -syn keyword ngxDirectiveThirdParty contained mongo_connect_timeout -syn keyword ngxDirectiveThirdParty contained mongo_json -syn keyword ngxDirectiveThirdParty contained mongo_next_upstream -syn keyword ngxDirectiveThirdParty contained mongo_pass -syn keyword ngxDirectiveThirdParty contained mongo_query -syn keyword ngxDirectiveThirdParty contained mongo_read_timeout -syn keyword ngxDirectiveThirdParty contained mongo_send_timeout - -" Nginx OCSP processing module designed for response caching -" https://github.com/kyprizel/nginx_ocsp_proxy-module -syn keyword ngxDirectiveThirdParty contained ocsp_cache_timeout -syn keyword ngxDirectiveThirdParty contained ocsp_proxy - -" Nginx OpenSSL version check at startup -" https://github.com/apcera/nginx-openssl-version -syn keyword ngxDirectiveThirdParty contained openssl_builddate_minimum -syn keyword ngxDirectiveThirdParty contained openssl_version_minimum - -" Automatic PageSpeed optimization module for Nginx -" https://github.com/pagespeed/ngx_pagespeed -syn keyword ngxDirectiveThirdParty contained pagespeed - -" PECL Memcache standard hashing compatible loadbalancer for Nginx -" https://github.com/replay/ngx_http_php_memcache_standard_balancer -syn keyword ngxDirectiveThirdParty contained hash_key - -" nginx module to parse php sessions -" https://github.com/replay/ngx_http_php_session -syn keyword ngxDirectiveThirdParty contained php_session_parse -syn keyword ngxDirectiveThirdParty contained php_session_strip_formatting - -" Nginx HTTP rDNS module -" https://github.com/flant/nginx-http-rdns -syn keyword ngxDirectiveThirdParty contained rdns -syn keyword ngxDirectiveThirdParty contained rdns_allow -syn keyword ngxDirectiveThirdParty contained rdns_deny - -" Streaming regular expression replacement in response bodies -" https://github.com/openresty/replace-filter-nginx-module -syn keyword ngxDirectiveThirdParty contained replace_filter -syn keyword ngxDirectiveThirdParty contained replace_filter_last_modified -syn keyword ngxDirectiveThirdParty contained replace_filter_max_buffered_size -syn keyword ngxDirectiveThirdParty contained replace_filter_skip -syn keyword ngxDirectiveThirdParty contained replace_filter_types - -" Link RRDtool's graphing facilities directly into nginx -" https://github.com/evanmiller/mod_rrd_graph -syn keyword ngxDirectiveThirdParty contained rrd_graph -syn keyword ngxDirectiveThirdParty contained rrd_graph_root - -" Module for nginx to proxy rtmp using http protocol -" https://github.com/kwojtek/nginx-rtmpt-proxy-module -syn keyword ngxDirectiveThirdParty contained rtmpt_proxy -syn keyword ngxDirectiveThirdParty contained rtmpt_proxy_http_timeout -syn keyword ngxDirectiveThirdParty contained rtmpt_proxy_rtmp_timeout -syn keyword ngxDirectiveThirdParty contained rtmpt_proxy_stat -syn keyword ngxDirectiveThirdParty contained rtmpt_proxy_stylesheet -syn keyword ngxDirectiveThirdParty contained rtmpt_proxy_target - -" Syntactically Awesome NGINX Module -" https://github.com/mneudert/sass-nginx-module -syn keyword ngxDirectiveThirdParty contained sass_compile -syn keyword ngxDirectiveThirdParty contained sass_error_log -syn keyword ngxDirectiveThirdParty contained sass_include_path -syn keyword ngxDirectiveThirdParty contained sass_indent -syn keyword ngxDirectiveThirdParty contained sass_is_indented_syntax -syn keyword ngxDirectiveThirdParty contained sass_linefeed -syn keyword ngxDirectiveThirdParty contained sass_output_style -syn keyword ngxDirectiveThirdParty contained sass_precision -syn keyword ngxDirectiveThirdParty contained sass_source_comments -syn keyword ngxDirectiveThirdParty contained sass_source_map_embed - -" Nginx Selective Cache Purge Module -" https://github.com/wandenberg/nginx-selective-cache-purge-module -syn keyword ngxDirectiveThirdParty contained selective_cache_purge_query -syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_database -syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_host -syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_password -syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_port -syn keyword ngxDirectiveThirdParty contained selective_cache_purge_redis_unix_socket - -" cconv nginx module -" https://github.com/liseen/set-cconv-nginx-module -syn keyword ngxDirectiveThirdParty contained set_cconv_to_simp -syn keyword ngxDirectiveThirdParty contained set_cconv_to_trad -syn keyword ngxDirectiveThirdParty contained set_pinyin_to_normal - -" Nginx module that allows the setting of variables to the value of a variety of hashes -" https://github.com/simpl/ngx_http_set_hash -syn keyword ngxDirectiveThirdParty contained set_md5 -syn keyword ngxDirectiveThirdParty contained set_md5_upper -syn keyword ngxDirectiveThirdParty contained set_murmur2 -syn keyword ngxDirectiveThirdParty contained set_murmur2_upper -syn keyword ngxDirectiveThirdParty contained set_sha1 -syn keyword ngxDirectiveThirdParty contained set_sha1_upper - -" Nginx module to set the language of a request based on a number of options -" https://github.com/simpl/ngx_http_set_lang -syn keyword ngxDirectiveThirdParty contained lang_cookie -syn keyword ngxDirectiveThirdParty contained lang_get_var -syn keyword ngxDirectiveThirdParty contained lang_host -syn keyword ngxDirectiveThirdParty contained lang_list -syn keyword ngxDirectiveThirdParty contained lang_post_var -syn keyword ngxDirectiveThirdParty contained lang_referer -syn keyword ngxDirectiveThirdParty contained set_lang -syn keyword ngxDirectiveThirdParty contained set_lang_method - -" Nginx Sorted Querystring Module -" https://github.com/wandenberg/nginx-sorted-querystring-module -syn keyword ngxDirectiveThirdParty contained sorted_querysting_filter_parameter - -" Nginx upstream module for Sphinx 2.x search daemon -" https://github.com/reeteshranjan/sphinx2-nginx-module -syn keyword ngxDirectiveThirdParty contained sphinx2_bind -syn keyword ngxDirectiveThirdParty contained sphinx2_buffer_size -syn keyword ngxDirectiveThirdParty contained sphinx2_connect_timeout -syn keyword ngxDirectiveThirdParty contained sphinx2_next_upstream -syn keyword ngxDirectiveThirdParty contained sphinx2_pass -syn keyword ngxDirectiveThirdParty contained sphinx2_read_timeout -syn keyword ngxDirectiveThirdParty contained sphinx2_send_timeout - -" Nginx module for retrieving user attributes and groups from SSSD -" https://github.com/veruu/ngx_sssd_info -syn keyword ngxDirectiveThirdParty contained sssd_info -syn keyword ngxDirectiveThirdParty contained sssd_info_attribute -syn keyword ngxDirectiveThirdParty contained sssd_info_attribute_separator -syn keyword ngxDirectiveThirdParty contained sssd_info_attributes -syn keyword ngxDirectiveThirdParty contained sssd_info_group -syn keyword ngxDirectiveThirdParty contained sssd_info_group_separator -syn keyword ngxDirectiveThirdParty contained sssd_info_groups -syn keyword ngxDirectiveThirdParty contained sssd_info_output_to - -" An nginx module for sending statistics to statsd -" https://github.com/zebrafishlabs/nginx-statsd -syn keyword ngxDirectiveThirdParty contained statsd_count -syn keyword ngxDirectiveThirdParty contained statsd_sample_rate -syn keyword ngxDirectiveThirdParty contained statsd_server -syn keyword ngxDirectiveThirdParty contained statsd_timing - -" ngx_stream_echo - TCP/stream echo module for NGINX (a port of the ngx_http_echo module) -" https://github.com/openresty/stream-echo-nginx-module -syn keyword ngxDirectiveThirdParty contained echo -syn keyword ngxDirectiveThirdParty contained echo_client_error_log_level -syn keyword ngxDirectiveThirdParty contained echo_discard_request -syn keyword ngxDirectiveThirdParty contained echo_duplicate -syn keyword ngxDirectiveThirdParty contained echo_flush_wait -syn keyword ngxDirectiveThirdParty contained echo_lingering_close -syn keyword ngxDirectiveThirdParty contained echo_lingering_time -syn keyword ngxDirectiveThirdParty contained echo_lingering_timeout -syn keyword ngxDirectiveThirdParty contained echo_read_buffer_size -syn keyword ngxDirectiveThirdParty contained echo_read_bytes -syn keyword ngxDirectiveThirdParty contained echo_read_line -syn keyword ngxDirectiveThirdParty contained echo_read_timeout -syn keyword ngxDirectiveThirdParty contained echo_request_data -syn keyword ngxDirectiveThirdParty contained echo_send_timeout -syn keyword ngxDirectiveThirdParty contained echo_sleep - -" Embed the power of Lua into NGINX TCP/UDP servers -" https://github.com/openresty/stream-lua-nginx-module -syn keyword ngxDirectiveThirdParty contained lua_add_variable -syn keyword ngxDirectiveThirdParty contained preread_by_lua_block -syn keyword ngxDirectiveThirdParty contained preread_by_lua_file -syn keyword ngxDirectiveThirdParty contained preread_by_lua_no_postpone - -" nginx-upsync-module -" https://github.com/weibocom/nginx-upsync-module -syn keyword ngxDirectiveThirdParty contained upstream_show -syn keyword ngxDirectiveThirdParty contained upsync -syn keyword ngxDirectiveThirdParty contained upsync_dump_path -syn keyword ngxDirectiveThirdParty contained upsync_lb - -" Whitespace stripper for nginx -" https://github.com/evanmiller/mod_strip -syn keyword ngxDirectiveThirdParty contained strip - -" Split one big HTTP/Range request to multiple subrange requesets -" https://github.com/Qihoo360/ngx_http_subrange_module -syn keyword ngxDirectiveThirdParty contained subrange - -" summarizer-nginx-module -" https://github.com/reeteshranjan/summarizer-nginx-module -syn keyword ngxDirectiveThirdParty contained summarizer_bind -syn keyword ngxDirectiveThirdParty contained summarizer_buffer_size -syn keyword ngxDirectiveThirdParty contained summarizer_connect_timeout -syn keyword ngxDirectiveThirdParty contained summarizer_next_upstream -syn keyword ngxDirectiveThirdParty contained summarizer_pass -syn keyword ngxDirectiveThirdParty contained summarizer_read_timeout -syn keyword ngxDirectiveThirdParty contained summarizer_send_timeout - -" nginx module providing API to communicate with supervisord and manage (start/stop) backends on-demand -" https://github.com/FRiCKLE/ngx_supervisord -syn keyword ngxDirectiveThirdParty contained supervisord -syn keyword ngxDirectiveThirdParty contained supervisord_inherit_backend_status -syn keyword ngxDirectiveThirdParty contained supervisord_name -syn keyword ngxDirectiveThirdParty contained supervisord_start -syn keyword ngxDirectiveThirdParty contained supervisord_stop - -" simple robot mitigation module using cookie based challenge/response technique. Not supported any more. -" https://github.com/kyprizel/testcookie-nginx-module -syn keyword ngxDirectiveThirdParty contained testcookie -syn keyword ngxDirectiveThirdParty contained testcookie_arg -syn keyword ngxDirectiveThirdParty contained testcookie_deny_keepalive -syn keyword ngxDirectiveThirdParty contained testcookie_domain -syn keyword ngxDirectiveThirdParty contained testcookie_expires -syn keyword ngxDirectiveThirdParty contained testcookie_fallback -syn keyword ngxDirectiveThirdParty contained testcookie_get_only -syn keyword ngxDirectiveThirdParty contained testcookie_httponly_flag -syn keyword ngxDirectiveThirdParty contained testcookie_https_location -syn keyword ngxDirectiveThirdParty contained testcookie_internal -syn keyword ngxDirectiveThirdParty contained testcookie_max_attempts -syn keyword ngxDirectiveThirdParty contained testcookie_name -syn keyword ngxDirectiveThirdParty contained testcookie_p3p -syn keyword ngxDirectiveThirdParty contained testcookie_pass -syn keyword ngxDirectiveThirdParty contained testcookie_path -syn keyword ngxDirectiveThirdParty contained testcookie_port_in_redirect -syn keyword ngxDirectiveThirdParty contained testcookie_redirect_via_refresh -syn keyword ngxDirectiveThirdParty contained testcookie_refresh_encrypt_cookie -syn keyword ngxDirectiveThirdParty contained testcookie_refresh_encrypt_cookie_iv -syn keyword ngxDirectiveThirdParty contained testcookie_refresh_encrypt_cookie_key -syn keyword ngxDirectiveThirdParty contained testcookie_refresh_status -syn keyword ngxDirectiveThirdParty contained testcookie_refresh_template -syn keyword ngxDirectiveThirdParty contained testcookie_samesite -syn keyword ngxDirectiveThirdParty contained testcookie_secret -syn keyword ngxDirectiveThirdParty contained testcookie_secure_flag -syn keyword ngxDirectiveThirdParty contained testcookie_session -syn keyword ngxDirectiveThirdParty contained testcookie_whitelist - -" ngx_http_types_filter_module -" https://github.com/flygoast/ngx_http_types_filter -syn keyword ngxDirectiveThirdParty contained types_filter -syn keyword ngxDirectiveThirdParty contained types_filter_use_default - -" A module allowing the nginx to use files embedded in a zip file -" https://github.com/youzee/nginx-unzip-module -syn keyword ngxDirectiveThirdParty contained file_in_unzip -syn keyword ngxDirectiveThirdParty contained file_in_unzip_archivefile -syn keyword ngxDirectiveThirdParty contained file_in_unzip_extract - -" An asynchronous domain name resolve module for nginx upstream -" https://github.com/wdaike/ngx_upstream_jdomain -syn keyword ngxDirectiveThirdParty contained jdomain - -" Nginx url encoding converting module -" https://github.com/vozlt/nginx-module-url -syn keyword ngxDirectiveThirdParty contained url_encoding_convert -syn keyword ngxDirectiveThirdParty contained url_encoding_convert_alloc_size -syn keyword ngxDirectiveThirdParty contained url_encoding_convert_alloc_size_x -syn keyword ngxDirectiveThirdParty contained url_encoding_convert_from -syn keyword ngxDirectiveThirdParty contained url_encoding_convert_phase -syn keyword ngxDirectiveThirdParty contained url_encoding_convert_to - -" A nginx module to match browsers and crawlers -" https://github.com/alibaba/nginx-http-user-agent -syn keyword ngxDirectiveThirdParty contained user_agent - -" nginx load-balancer module implementing ketama consistent hashing -" https://github.com/flygoast/ngx_http_upstream_ketama_chash -syn keyword ngxDirectiveThirdParty contained ketama_chash - -" nginx-sticky-module-ng -" https://github.com/ayty-adrianomartins/nginx-sticky-module-ng -syn keyword ngxDirectiveThirdParty contained sticky_no_fallback - -" dynamic linking and call the function of your application -" https://github.com/Taymindis/nginx-link-function -syn keyword ngxDirectiveThirdParty contained ngx_link_func_add_prop -syn keyword ngxDirectiveThirdParty contained ngx_link_func_add_req_header -syn keyword ngxDirectiveThirdParty contained ngx_link_func_ca_cert -syn keyword ngxDirectiveThirdParty contained ngx_link_func_call -syn keyword ngxDirectiveThirdParty contained ngx_link_func_download_link_lib -syn keyword ngxDirectiveThirdParty contained ngx_link_func_lib -syn keyword ngxDirectiveThirdParty contained ngx_link_func_shm_size -syn keyword ngxDirectiveThirdParty contained ngx_link_func_subrequest - -" purge content from FastCGI, proxy, SCGI and uWSGI caches -" https://github.com/torden/ngx_cache_purge -syn keyword ngxDirectiveThirdParty contained cache_purge_response_type - -" set the flags "HttpOnly", "secure" and "SameSite" for cookies -" https://github.com/AirisX/nginx_cookie_flag_module -syn keyword ngxDirectiveThirdParty contained set_cookie_flag - -" Embed websockify into Nginx (convert any tcp connection into websocket) " https://github.com/tg123/websockify-nginx-module syn keyword ngxDirectiveThirdParty contained websockify_buffer_size syn keyword ngxDirectiveThirdParty contained websockify_connect_timeout @@ -2415,54 +1941,13 @@ syn keyword ngxDirectiveThirdParty contained websockify_pass syn keyword ngxDirectiveThirdParty contained websockify_read_timeout syn keyword ngxDirectiveThirdParty contained websockify_send_timeout -" IP2Location Nginx -" https://github.com/ip2location/ip2location-nginx -syn keyword ngxDirectiveThirdParty contained ip2location_addresstype -syn keyword ngxDirectiveThirdParty contained ip2location_areacode -syn keyword ngxDirectiveThirdParty contained ip2location_category -syn keyword ngxDirectiveThirdParty contained ip2location_city -syn keyword ngxDirectiveThirdParty contained ip2location_country_long -syn keyword ngxDirectiveThirdParty contained ip2location_country_short -syn keyword ngxDirectiveThirdParty contained ip2location_domain -syn keyword ngxDirectiveThirdParty contained ip2location_elevation -syn keyword ngxDirectiveThirdParty contained ip2location_iddcode -syn keyword ngxDirectiveThirdParty contained ip2location_isp -syn keyword ngxDirectiveThirdParty contained ip2location_latitude -syn keyword ngxDirectiveThirdParty contained ip2location_longitude -syn keyword ngxDirectiveThirdParty contained ip2location_mcc -syn keyword ngxDirectiveThirdParty contained ip2location_mnc -syn keyword ngxDirectiveThirdParty contained ip2location_mobilebrand -syn keyword ngxDirectiveThirdParty contained ip2location_netspeed -syn keyword ngxDirectiveThirdParty contained ip2location_proxy -syn keyword ngxDirectiveThirdParty contained ip2location_proxy_recursive -syn keyword ngxDirectiveThirdParty contained ip2location_region -syn keyword ngxDirectiveThirdParty contained ip2location_timezone -syn keyword ngxDirectiveThirdParty contained ip2location_usagetype -syn keyword ngxDirectiveThirdParty contained ip2location_weatherstationcode -syn keyword ngxDirectiveThirdParty contained ip2location_weatherstationname -syn keyword ngxDirectiveThirdParty contained ip2location_zipcode - -" IP2Proxy module for Nginx -" https://github.com/ip2location/ip2proxy-nginx -syn keyword ngxDirectiveThirdParty contained ip2proxy_as -syn keyword ngxDirectiveThirdParty contained ip2proxy_asn -syn keyword ngxDirectiveThirdParty contained ip2proxy_city -syn keyword ngxDirectiveThirdParty contained ip2proxy_country_long -syn keyword ngxDirectiveThirdParty contained ip2proxy_country_short -syn keyword ngxDirectiveThirdParty contained ip2proxy_database -syn keyword ngxDirectiveThirdParty contained ip2proxy_domain -syn keyword ngxDirectiveThirdParty contained ip2proxy_isp -syn keyword ngxDirectiveThirdParty contained ip2proxy_is_proxy -syn keyword ngxDirectiveThirdParty contained ip2proxy_last_seen -syn keyword ngxDirectiveThirdParty contained ip2proxy_provider -syn keyword ngxDirectiveThirdParty contained ip2proxy_proxy -syn keyword ngxDirectiveThirdParty contained ip2proxy_proxy_recursive -syn keyword ngxDirectiveThirdParty contained ip2proxy_proxy_type -syn keyword ngxDirectiveThirdParty contained ip2proxy_region -syn keyword ngxDirectiveThirdParty contained ip2proxy_threat -syn keyword ngxDirectiveThirdParty contained ip2proxy_usage_type - - +" https://github.com/vozlt/nginx-module-sts +syn keyword ngxDirectiveThirdParty contained stream_server_traffic_status +syn keyword ngxDirectiveThirdParty contained stream_server_traffic_status_average_method +syn keyword ngxDirectiveThirdParty contained stream_server_traffic_status_display +syn keyword ngxDirectiveThirdParty contained stream_server_traffic_status_display_format +syn keyword ngxDirectiveThirdParty contained stream_server_traffic_status_display_jsonp +syn keyword ngxDirectiveThirdParty contained stream_server_traffic_status_zone " highlight diff --git a/src/core/nginx.c b/src/core/nginx.c index 48a20e9..0deb27b 100644 --- a/src/core/nginx.c +++ b/src/core/nginx.c @@ -13,6 +13,7 @@ static void ngx_show_version_info(void); static ngx_int_t ngx_add_inherited_sockets(ngx_cycle_t *cycle); static void ngx_cleanup_environment(void *data); +static void ngx_cleanup_environment_variable(void *data); static ngx_int_t ngx_get_options(int argc, char *const *argv); static ngx_int_t ngx_process_options(ngx_cycle_t *cycle); static ngx_int_t ngx_save_argv(ngx_cycle_t *cycle, int argc, char *const *argv); @@ -518,7 +519,8 @@ ngx_add_inherited_sockets(ngx_cycle_t *cycle) char ** ngx_set_environment(ngx_cycle_t *cycle, ngx_uint_t *last) { - char **p, **env; + char **p, **env, *str; + size_t len; ngx_str_t *var; ngx_uint_t i, n; ngx_core_conf_t *ccf; @@ -600,7 +602,31 @@ tz_found: for (i = 0; i < ccf->env.nelts; i++) { if (var[i].data[var[i].len] == '=') { - env[n++] = (char *) var[i].data; + + if (last) { + env[n++] = (char *) var[i].data; + continue; + } + + cln = ngx_pool_cleanup_add(cycle->pool, 0); + if (cln == NULL) { + return NULL; + } + + len = ngx_strlen(var[i].data) + 1; + + str = ngx_alloc(len, cycle->log); + if (str == NULL) { + return NULL; + } + + ngx_memcpy(str, var[i].data, len); + + cln->handler = ngx_cleanup_environment_variable; + cln->data = str; + + env[n++] = str; + continue; } @@ -645,6 +671,29 @@ ngx_cleanup_environment(void *data) } +static void +ngx_cleanup_environment_variable(void *data) +{ + char *var = data; + + char **p; + + for (p = environ; *p; p++) { + + /* + * if an environment variable is still used, as it happens on exit, + * the only option is to leak it + */ + + if (*p == var) { + return; + } + } + + ngx_free(var); +} + + ngx_pid_t ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv) { @@ -680,6 +729,9 @@ ngx_exec_new_binary(ngx_cycle_t *cycle, char *const *argv) ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { + if (ls[i].ignore) { + continue; + } p = ngx_sprintf(p, "%ud;", ls[i].fd); } diff --git a/src/core/nginx.h b/src/core/nginx.h index 9f8756b..ef000e0 100644 --- a/src/core/nginx.h +++ b/src/core/nginx.h @@ -9,8 +9,8 @@ #define _NGINX_H_INCLUDED_ -#define nginx_version 1022001 -#define NGINX_VERSION "1.22.1" +#define nginx_version 1026003 +#define NGINX_VERSION "1.26.3" #define NGINX_VER "nginx/" NGINX_VERSION #ifdef NGX_BUILD diff --git a/src/core/ngx_bpf.c b/src/core/ngx_bpf.c new file mode 100644 index 0000000..363a02c --- /dev/null +++ b/src/core/ngx_bpf.c @@ -0,0 +1,143 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + +#define NGX_BPF_LOGBUF_SIZE (16 * 1024) + + +static ngx_inline int +ngx_bpf(enum bpf_cmd cmd, union bpf_attr *attr, unsigned int size) +{ + return syscall(__NR_bpf, cmd, attr, size); +} + + +void +ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, int fd) +{ + ngx_uint_t i; + ngx_bpf_reloc_t *rl; + + rl = program->relocs; + + for (i = 0; i < program->nrelocs; i++) { + if (ngx_strcmp(rl[i].name, symbol) == 0) { + program->ins[rl[i].offset].src_reg = 1; + program->ins[rl[i].offset].imm = fd; + } + } +} + + +int +ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program) +{ + int fd; + union bpf_attr attr; +#if (NGX_DEBUG) + char buf[NGX_BPF_LOGBUF_SIZE]; +#endif + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.license = (uintptr_t) program->license; + attr.prog_type = program->type; + attr.insns = (uintptr_t) program->ins; + attr.insn_cnt = program->nins; + +#if (NGX_DEBUG) + /* for verifier errors */ + attr.log_buf = (uintptr_t) buf; + attr.log_size = NGX_BPF_LOGBUF_SIZE; + attr.log_level = 1; +#endif + + fd = ngx_bpf(BPF_PROG_LOAD, &attr, sizeof(attr)); + if (fd < 0) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "failed to load BPF program"); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "bpf verifier: %s", buf); + + return -1; + } + + return fd; +} + + +int +ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size, + int value_size, int max_entries, uint32_t map_flags) +{ + int fd; + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_type = type; + attr.key_size = key_size; + attr.value_size = value_size; + attr.max_entries = max_entries; + attr.map_flags = map_flags; + + fd = ngx_bpf(BPF_MAP_CREATE, &attr, sizeof(attr)); + if (fd < 0) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "failed to create BPF map"); + return NGX_ERROR; + } + + return fd; +} + + +int +ngx_bpf_map_update(int fd, const void *key, const void *value, uint64_t flags) +{ + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_fd = fd; + attr.key = (uintptr_t) key; + attr.value = (uintptr_t) value; + attr.flags = flags; + + return ngx_bpf(BPF_MAP_UPDATE_ELEM, &attr, sizeof(attr)); +} + + +int +ngx_bpf_map_delete(int fd, const void *key) +{ + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_fd = fd; + attr.key = (uintptr_t) key; + + return ngx_bpf(BPF_MAP_DELETE_ELEM, &attr, sizeof(attr)); +} + + +int +ngx_bpf_map_lookup(int fd, const void *key, void *value) +{ + union bpf_attr attr; + + ngx_memzero(&attr, sizeof(union bpf_attr)); + + attr.map_fd = fd; + attr.key = (uintptr_t) key; + attr.value = (uintptr_t) value; + + return ngx_bpf(BPF_MAP_LOOKUP_ELEM, &attr, sizeof(attr)); +} diff --git a/src/core/ngx_bpf.h b/src/core/ngx_bpf.h new file mode 100644 index 0000000..f62a36e --- /dev/null +++ b/src/core/ngx_bpf.h @@ -0,0 +1,43 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_BPF_H_INCLUDED_ +#define _NGX_BPF_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + +#include <linux/bpf.h> + + +typedef struct { + char *name; + int offset; +} ngx_bpf_reloc_t; + +typedef struct { + char *license; + enum bpf_prog_type type; + struct bpf_insn *ins; + size_t nins; + ngx_bpf_reloc_t *relocs; + size_t nrelocs; +} ngx_bpf_program_t; + + +void ngx_bpf_program_link(ngx_bpf_program_t *program, const char *symbol, + int fd); +int ngx_bpf_load_program(ngx_log_t *log, ngx_bpf_program_t *program); + +int ngx_bpf_map_create(ngx_log_t *log, enum bpf_map_type type, int key_size, + int value_size, int max_entries, uint32_t map_flags); +int ngx_bpf_map_update(int fd, const void *key, const void *value, + uint64_t flags); +int ngx_bpf_map_delete(int fd, const void *key); +int ngx_bpf_map_lookup(int fd, const void *key, void *value); + +#endif /* _NGX_BPF_H_INCLUDED_ */ diff --git a/src/core/ngx_conf_file.c b/src/core/ngx_conf_file.c index fec7bb8..197704b 100644 --- a/src/core/ngx_conf_file.c +++ b/src/core/ngx_conf_file.c @@ -544,8 +544,8 @@ ngx_conf_read_token(ngx_conf_t *cf) } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "unexpected end of file, " - "expecting \";\" or \"}\""); + "unexpected end of file, " + "expecting \";\" or \"}\""); return NGX_ERROR; } diff --git a/src/core/ngx_connection.c b/src/core/ngx_connection.c index fe729a7..75809d9 100644 --- a/src/core/ngx_connection.c +++ b/src/core/ngx_connection.c @@ -660,7 +660,7 @@ ngx_open_listening_sockets(ngx_cycle_t *cycle) /* * on OpenVZ after suspend/resume EADDRINUSE * may be returned by listen() instead of bind(), see - * https://bugzilla.openvz.org/show_bug.cgi?id=2470 + * https://bugs.openvz.org/browse/OVZ-5587 */ if (err != NGX_EADDRINUSE || !ngx_test_config) { @@ -1013,6 +1013,78 @@ ngx_configure_listening_sockets(ngx_cycle_t *cycle) } } +#endif + +#if (NGX_HAVE_IP_MTU_DISCOVER) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { + value = IP_PMTUDISC_DO; + + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IP_MTU_DISCOVER) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IP, IP_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IP_DONTFRAG) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#endif + +#if (NGX_HAVE_INET6) + +#if (NGX_HAVE_IPV6_MTU_DISCOVER) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { + value = IPV6_PMTUDISC_DO; + + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IPV6_MTU_DISCOVER) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#elif (NGX_HAVE_IP_DONTFRAG) + + if (ls[i].quic && ls[i].sockaddr->sa_family == AF_INET6) { + value = 1; + + if (setsockopt(ls[i].fd, IPPROTO_IPV6, IPV6_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_socket_errno, + "setsockopt(IPV6_DONTFRAG) " + "for %V failed, ignored", + &ls[i].addr_text); + } + } + +#endif + #endif } @@ -1037,6 +1109,12 @@ ngx_close_listening_sockets(ngx_cycle_t *cycle) ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { +#if (NGX_QUIC) + if (ls[i].quic) { + continue; + } +#endif + c = ls[i].connection; if (c) { @@ -1505,6 +1583,10 @@ ngx_connection_error(ngx_connection_t *c, ngx_err_t err, char *text) } #endif + if (err == NGX_EMSGSIZE && c->log_error == NGX_ERROR_IGNORE_EMSGSIZE) { + return 0; + } + if (err == 0 || err == NGX_ECONNRESET #if (NGX_WIN32) @@ -1522,6 +1604,7 @@ ngx_connection_error(ngx_connection_t *c, ngx_err_t err, char *text) { switch (c->log_error) { + case NGX_ERROR_IGNORE_EMSGSIZE: case NGX_ERROR_IGNORE_EINVAL: case NGX_ERROR_IGNORE_ECONNRESET: case NGX_ERROR_INFO: diff --git a/src/core/ngx_connection.h b/src/core/ngx_connection.h index 8cc1475..84dd804 100644 --- a/src/core/ngx_connection.h +++ b/src/core/ngx_connection.h @@ -73,6 +73,7 @@ struct ngx_listening_s { unsigned reuseport:1; unsigned add_reuseport:1; unsigned keepalive:2; + unsigned quic:1; unsigned deferred_accept:1; unsigned delete_deferred:1; @@ -96,7 +97,8 @@ typedef enum { NGX_ERROR_ERR, NGX_ERROR_INFO, NGX_ERROR_IGNORE_ECONNRESET, - NGX_ERROR_IGNORE_EINVAL + NGX_ERROR_IGNORE_EINVAL, + NGX_ERROR_IGNORE_EMSGSIZE } ngx_connection_log_error_e; @@ -147,6 +149,10 @@ struct ngx_connection_s { ngx_proxy_protocol_t *proxy_protocol; +#if (NGX_QUIC || NGX_COMPAT) + ngx_quic_stream_t *quic; +#endif + #if (NGX_SSL || NGX_COMPAT) ngx_ssl_connection_t *ssl; #endif @@ -172,6 +178,7 @@ struct ngx_connection_s { unsigned timedout:1; unsigned error:1; unsigned destroyed:1; + unsigned pipeline:1; unsigned idle:1; unsigned reusable:1; @@ -184,6 +191,7 @@ struct ngx_connection_s { unsigned tcp_nopush:2; /* ngx_connection_tcp_nopush_e */ unsigned need_last_buf:1; + unsigned need_flush_buf:1; #if (NGX_HAVE_SENDFILE_NODISKIO || NGX_COMPAT) unsigned busy_count:2; diff --git a/src/core/ngx_core.h b/src/core/ngx_core.h index 7ecdca0..88db7dc 100644 --- a/src/core/ngx_core.h +++ b/src/core/ngx_core.h @@ -27,6 +27,7 @@ typedef struct ngx_connection_s ngx_connection_t; typedef struct ngx_thread_task_s ngx_thread_task_t; typedef struct ngx_ssl_s ngx_ssl_t; typedef struct ngx_proxy_protocol_s ngx_proxy_protocol_t; +typedef struct ngx_quic_stream_s ngx_quic_stream_t; typedef struct ngx_ssl_connection_s ngx_ssl_connection_t; typedef struct ngx_udp_connection_s ngx_udp_connection_t; @@ -82,6 +83,9 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); #include <ngx_resolver.h> #if (NGX_OPENSSL) #include <ngx_event_openssl.h> +#if (NGX_QUIC) +#include <ngx_event_quic.h> +#endif #endif #include <ngx_process_cycle.h> #include <ngx_conf_file.h> @@ -91,6 +95,9 @@ typedef void (*ngx_connection_handler_pt)(ngx_connection_t *c); #include <ngx_connection.h> #include <ngx_syslog.h> #include <ngx_proxy_protocol.h> +#if (NGX_HAVE_BPF) +#include <ngx_bpf.h> +#endif #define LF (u_char) '\n' diff --git a/src/core/ngx_hash.h b/src/core/ngx_hash.h index abc3cbe..3b57099 100644 --- a/src/core/ngx_hash.h +++ b/src/core/ngx_hash.h @@ -89,12 +89,15 @@ typedef struct { } ngx_hash_keys_arrays_t; -typedef struct { +typedef struct ngx_table_elt_s ngx_table_elt_t; + +struct ngx_table_elt_s { ngx_uint_t hash; ngx_str_t key; ngx_str_t value; u_char *lowcase_key; -} ngx_table_elt_t; + ngx_table_elt_t *next; +}; void *ngx_hash_find(ngx_hash_t *hash, ngx_uint_t key, u_char *name, size_t len); diff --git a/src/core/ngx_inet.c b/src/core/ngx_inet.c index 4228504..acb2ef4 100644 --- a/src/core/ngx_inet.c +++ b/src/core/ngx_inet.c @@ -507,7 +507,7 @@ ngx_cidr_match(struct sockaddr *sa, ngx_array_t *cidrs) p = inaddr6->s6_addr; - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; diff --git a/src/core/ngx_module.h b/src/core/ngx_module.h index 6fb4554..a415cd6 100644 --- a/src/core/ngx_module.h +++ b/src/core/ngx_module.h @@ -107,7 +107,12 @@ #endif #define NGX_MODULE_SIGNATURE_17 "0" + +#if (NGX_QUIC || NGX_COMPAT) +#define NGX_MODULE_SIGNATURE_18 "1" +#else #define NGX_MODULE_SIGNATURE_18 "0" +#endif #if (NGX_HAVE_OPENAT) #define NGX_MODULE_SIGNATURE_19 "1" diff --git a/src/core/ngx_output_chain.c b/src/core/ngx_output_chain.c index 8570742..a46209c 100644 --- a/src/core/ngx_output_chain.c +++ b/src/core/ngx_output_chain.c @@ -117,7 +117,10 @@ ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in) ngx_debug_point(); - ctx->in = ctx->in->next; + cl = ctx->in; + ctx->in = cl->next; + + ngx_free_chain(ctx->pool, cl); continue; } @@ -203,7 +206,10 @@ ngx_output_chain(ngx_output_chain_ctx_t *ctx, ngx_chain_t *in) /* delete the completed buf from the ctx->in chain */ if (ngx_buf_size(ctx->in->buf) == 0) { - ctx->in = ctx->in->next; + cl = ctx->in; + ctx->in = cl->next; + + ngx_free_chain(ctx->pool, cl); } cl = ngx_alloc_chain_link(ctx->pool); diff --git a/src/core/ngx_proxy_protocol.c b/src/core/ngx_proxy_protocol.c index 7a9e7f9..49888b9 100644 --- a/src/core/ngx_proxy_protocol.c +++ b/src/core/ngx_proxy_protocol.c @@ -13,7 +13,15 @@ #define NGX_PROXY_PROTOCOL_AF_INET6 2 -#define ngx_proxy_protocol_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_proxy_protocol_parse_uint16(p) \ + ( ((uint16_t) (p)[0] << 8) \ + + ( (p)[1]) ) + +#define ngx_proxy_protocol_parse_uint32(p) \ + ( ((uint32_t) (p)[0] << 24) \ + + ( (p)[1] << 16) \ + + ( (p)[2] << 8) \ + + ( (p)[3]) ) typedef struct { @@ -40,12 +48,52 @@ typedef struct { } ngx_proxy_protocol_inet6_addrs_t; +typedef struct { + u_char type; + u_char len[2]; +} ngx_proxy_protocol_tlv_t; + + +typedef struct { + u_char client; + u_char verify[4]; +} ngx_proxy_protocol_tlv_ssl_t; + + +typedef struct { + ngx_str_t name; + ngx_uint_t type; +} ngx_proxy_protocol_tlv_entry_t; + + static u_char *ngx_proxy_protocol_read_addr(ngx_connection_t *c, u_char *p, u_char *last, ngx_str_t *addr); static u_char *ngx_proxy_protocol_read_port(u_char *p, u_char *last, in_port_t *port, u_char sep); static u_char *ngx_proxy_protocol_v2_read(ngx_connection_t *c, u_char *buf, u_char *last); +static ngx_int_t ngx_proxy_protocol_lookup_tlv(ngx_connection_t *c, + ngx_str_t *tlvs, ngx_uint_t type, ngx_str_t *value); + + +static ngx_proxy_protocol_tlv_entry_t ngx_proxy_protocol_tlv_entries[] = { + { ngx_string("alpn"), 0x01 }, + { ngx_string("authority"), 0x02 }, + { ngx_string("unique_id"), 0x05 }, + { ngx_string("ssl"), 0x20 }, + { ngx_string("netns"), 0x30 }, + { ngx_null_string, 0x00 } +}; + + +static ngx_proxy_protocol_tlv_entry_t ngx_proxy_protocol_tlv_ssl_entries[] = { + { ngx_string("version"), 0x21 }, + { ngx_string("cn"), 0x22 }, + { ngx_string("cipher"), 0x23 }, + { ngx_string("sig_alg"), 0x24 }, + { ngx_string("key_alg"), 0x25 }, + { ngx_null_string, 0x00 } +}; u_char * @@ -61,7 +109,7 @@ ngx_proxy_protocol_read(ngx_connection_t *c, u_char *buf, u_char *last) len = last - buf; if (len >= sizeof(ngx_proxy_protocol_header_t) - && memcmp(p, signature, sizeof(signature) - 1) == 0) + && ngx_memcmp(p, signature, sizeof(signature) - 1) == 0) { return ngx_proxy_protocol_v2_read(c, buf, last); } @@ -139,8 +187,14 @@ skip: invalid: + for (p = buf; p < last; p++) { + if (*p == CR || *p == LF) { + break; + } + } + ngx_log_error(NGX_LOG_ERR, c->log, 0, - "broken header: \"%*s\"", (size_t) (last - buf), buf); + "broken header: \"%*s\"", (size_t) (p - buf), buf); return NULL; } @@ -227,7 +281,9 @@ ngx_proxy_protocol_write(ngx_connection_t *c, u_char *buf, u_char *last) { ngx_uint_t port, lport; - if (last - buf < NGX_PROXY_PROTOCOL_MAX_HEADER) { + if (last - buf < NGX_PROXY_PROTOCOL_V1_MAX_HEADER) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "too small buffer for PROXY protocol"); return NULL; } @@ -340,11 +396,11 @@ ngx_proxy_protocol_v2_read(ngx_connection_t *c, u_char *buf, u_char *last) src_sockaddr.sockaddr_in.sin_family = AF_INET; src_sockaddr.sockaddr_in.sin_port = 0; - memcpy(&src_sockaddr.sockaddr_in.sin_addr, in->src_addr, 4); + ngx_memcpy(&src_sockaddr.sockaddr_in.sin_addr, in->src_addr, 4); dst_sockaddr.sockaddr_in.sin_family = AF_INET; dst_sockaddr.sockaddr_in.sin_port = 0; - memcpy(&dst_sockaddr.sockaddr_in.sin_addr, in->dst_addr, 4); + ngx_memcpy(&dst_sockaddr.sockaddr_in.sin_addr, in->dst_addr, 4); pp->src_port = ngx_proxy_protocol_parse_uint16(in->src_port); pp->dst_port = ngx_proxy_protocol_parse_uint16(in->dst_port); @@ -367,11 +423,11 @@ ngx_proxy_protocol_v2_read(ngx_connection_t *c, u_char *buf, u_char *last) src_sockaddr.sockaddr_in6.sin6_family = AF_INET6; src_sockaddr.sockaddr_in6.sin6_port = 0; - memcpy(&src_sockaddr.sockaddr_in6.sin6_addr, in6->src_addr, 16); + ngx_memcpy(&src_sockaddr.sockaddr_in6.sin6_addr, in6->src_addr, 16); dst_sockaddr.sockaddr_in6.sin6_family = AF_INET6; dst_sockaddr.sockaddr_in6.sin6_port = 0; - memcpy(&dst_sockaddr.sockaddr_in6.sin6_addr, in6->dst_addr, 16); + ngx_memcpy(&dst_sockaddr.sockaddr_in6.sin6_addr, in6->dst_addr, 16); pp->src_port = ngx_proxy_protocol_parse_uint16(in6->src_port); pp->dst_port = ngx_proxy_protocol_parse_uint16(in6->dst_port); @@ -412,11 +468,147 @@ ngx_proxy_protocol_v2_read(ngx_connection_t *c, u_char *buf, u_char *last) &pp->src_addr, pp->src_port, &pp->dst_addr, pp->dst_port); if (buf < end) { - ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0, - "PROXY protocol v2 %z bytes of tlv ignored", end - buf); + pp->tlvs.data = ngx_pnalloc(c->pool, end - buf); + if (pp->tlvs.data == NULL) { + return NULL; + } + + ngx_memcpy(pp->tlvs.data, buf, end - buf); + pp->tlvs.len = end - buf; } c->proxy_protocol = pp; return end; } + + +ngx_int_t +ngx_proxy_protocol_get_tlv(ngx_connection_t *c, ngx_str_t *name, + ngx_str_t *value) +{ + u_char *p; + size_t n; + uint32_t verify; + ngx_str_t ssl, *tlvs; + ngx_int_t rc, type; + ngx_proxy_protocol_tlv_ssl_t *tlv_ssl; + ngx_proxy_protocol_tlv_entry_t *te; + + if (c->proxy_protocol == NULL) { + return NGX_DECLINED; + } + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0, + "PROXY protocol v2 get tlv \"%V\"", name); + + te = ngx_proxy_protocol_tlv_entries; + tlvs = &c->proxy_protocol->tlvs; + + p = name->data; + n = name->len; + + if (n >= 4 && p[0] == 's' && p[1] == 's' && p[2] == 'l' && p[3] == '_') { + + rc = ngx_proxy_protocol_lookup_tlv(c, tlvs, 0x20, &ssl); + if (rc != NGX_OK) { + return rc; + } + + if (ssl.len < sizeof(ngx_proxy_protocol_tlv_ssl_t)) { + return NGX_ERROR; + } + + p += 4; + n -= 4; + + if (n == 6 && ngx_strncmp(p, "verify", 6) == 0) { + + tlv_ssl = (ngx_proxy_protocol_tlv_ssl_t *) ssl.data; + verify = ngx_proxy_protocol_parse_uint32(tlv_ssl->verify); + + value->data = ngx_pnalloc(c->pool, NGX_INT32_LEN); + if (value->data == NULL) { + return NGX_ERROR; + } + + value->len = ngx_sprintf(value->data, "%uD", verify) + - value->data; + return NGX_OK; + } + + ssl.data += sizeof(ngx_proxy_protocol_tlv_ssl_t); + ssl.len -= sizeof(ngx_proxy_protocol_tlv_ssl_t); + + te = ngx_proxy_protocol_tlv_ssl_entries; + tlvs = &ssl; + } + + if (n >= 2 && p[0] == '0' && p[1] == 'x') { + + type = ngx_hextoi(p + 2, n - 2); + if (type == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "invalid PROXY protocol TLV \"%V\"", name); + return NGX_ERROR; + } + + return ngx_proxy_protocol_lookup_tlv(c, tlvs, type, value); + } + + for ( /* void */ ; te->type; te++) { + if (te->name.len == n && ngx_strncmp(te->name.data, p, n) == 0) { + return ngx_proxy_protocol_lookup_tlv(c, tlvs, te->type, value); + } + } + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "unknown PROXY protocol TLV \"%V\"", name); + + return NGX_DECLINED; +} + + +static ngx_int_t +ngx_proxy_protocol_lookup_tlv(ngx_connection_t *c, ngx_str_t *tlvs, + ngx_uint_t type, ngx_str_t *value) +{ + u_char *p; + size_t n, len; + ngx_proxy_protocol_tlv_t *tlv; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, c->log, 0, + "PROXY protocol v2 lookup tlv:%02xi", type); + + p = tlvs->data; + n = tlvs->len; + + while (n) { + if (n < sizeof(ngx_proxy_protocol_tlv_t)) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "broken PROXY protocol TLV"); + return NGX_ERROR; + } + + tlv = (ngx_proxy_protocol_tlv_t *) p; + len = ngx_proxy_protocol_parse_uint16(tlv->len); + + p += sizeof(ngx_proxy_protocol_tlv_t); + n -= sizeof(ngx_proxy_protocol_tlv_t); + + if (n < len) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "broken PROXY protocol TLV"); + return NGX_ERROR; + } + + if (tlv->type == type) { + value->data = p; + value->len = len; + return NGX_OK; + } + + p += len; + n -= len; + } + + return NGX_DECLINED; +} diff --git a/src/core/ngx_proxy_protocol.h b/src/core/ngx_proxy_protocol.h index b716220..d1749f5 100644 --- a/src/core/ngx_proxy_protocol.h +++ b/src/core/ngx_proxy_protocol.h @@ -13,7 +13,8 @@ #include <ngx_core.h> -#define NGX_PROXY_PROTOCOL_MAX_HEADER 107 +#define NGX_PROXY_PROTOCOL_V1_MAX_HEADER 107 +#define NGX_PROXY_PROTOCOL_MAX_HEADER 4096 struct ngx_proxy_protocol_s { @@ -21,6 +22,7 @@ struct ngx_proxy_protocol_s { ngx_str_t dst_addr; in_port_t src_port; in_port_t dst_port; + ngx_str_t tlvs; }; @@ -28,6 +30,8 @@ u_char *ngx_proxy_protocol_read(ngx_connection_t *c, u_char *buf, u_char *last); u_char *ngx_proxy_protocol_write(ngx_connection_t *c, u_char *buf, u_char *last); +ngx_int_t ngx_proxy_protocol_get_tlv(ngx_connection_t *c, ngx_str_t *name, + ngx_str_t *value); #endif /* _NGX_PROXY_PROTOCOL_H_INCLUDED_ */ diff --git a/src/core/ngx_queue.c b/src/core/ngx_queue.c index 3cacaf3..3d1d589 100644 --- a/src/core/ngx_queue.c +++ b/src/core/ngx_queue.c @@ -9,6 +9,10 @@ #include <ngx_core.h> +static void ngx_queue_merge(ngx_queue_t *queue, ngx_queue_t *tail, + ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)); + + /* * find the middle queue element if the queue has odd number of elements * or the first element of the queue's second part otherwise @@ -45,13 +49,13 @@ ngx_queue_middle(ngx_queue_t *queue) } -/* the stable insertion sort */ +/* the stable merge sort */ void ngx_queue_sort(ngx_queue_t *queue, ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)) { - ngx_queue_t *q, *prev, *next; + ngx_queue_t *q, tail; q = ngx_queue_head(queue); @@ -59,22 +63,44 @@ ngx_queue_sort(ngx_queue_t *queue, return; } - for (q = ngx_queue_next(q); q != ngx_queue_sentinel(queue); q = next) { + q = ngx_queue_middle(queue); + + ngx_queue_split(queue, q, &tail); + + ngx_queue_sort(queue, cmp); + ngx_queue_sort(&tail, cmp); + + ngx_queue_merge(queue, &tail, cmp); +} + - prev = ngx_queue_prev(q); - next = ngx_queue_next(q); +static void +ngx_queue_merge(ngx_queue_t *queue, ngx_queue_t *tail, + ngx_int_t (*cmp)(const ngx_queue_t *, const ngx_queue_t *)) +{ + ngx_queue_t *q1, *q2; - ngx_queue_remove(q); + q1 = ngx_queue_head(queue); + q2 = ngx_queue_head(tail); - do { - if (cmp(prev, q) <= 0) { - break; - } + for ( ;; ) { + if (q1 == ngx_queue_sentinel(queue)) { + ngx_queue_add(queue, tail); + break; + } - prev = ngx_queue_prev(prev); + if (q2 == ngx_queue_sentinel(tail)) { + break; + } + + if (cmp(q1, q2) <= 0) { + q1 = ngx_queue_next(q1); + continue; + } - } while (prev != ngx_queue_sentinel(queue)); + ngx_queue_remove(q2); + ngx_queue_insert_before(q1, q2); - ngx_queue_insert_after(prev, q); + q2 = ngx_queue_head(tail); } } diff --git a/src/core/ngx_queue.h b/src/core/ngx_queue.h index 038bf12..0f82f17 100644 --- a/src/core/ngx_queue.h +++ b/src/core/ngx_queue.h @@ -47,6 +47,9 @@ struct ngx_queue_s { (h)->prev = x +#define ngx_queue_insert_before ngx_queue_insert_tail + + #define ngx_queue_head(h) \ (h)->next diff --git a/src/core/ngx_regex.c b/src/core/ngx_regex.c index bebf3b6..5b13c5d 100644 --- a/src/core/ngx_regex.c +++ b/src/core/ngx_regex.c @@ -600,6 +600,8 @@ ngx_regex_cleanup(void *data) * the new cycle, these will be re-allocated. */ + ngx_regex_malloc_init(NULL); + if (ngx_regex_compile_context) { pcre2_compile_context_free(ngx_regex_compile_context); ngx_regex_compile_context = NULL; @@ -611,6 +613,8 @@ ngx_regex_cleanup(void *data) ngx_regex_match_data_size = 0; } + ngx_regex_malloc_done(); + #endif } @@ -706,9 +710,6 @@ ngx_regex_module_init(ngx_cycle_t *cycle) ngx_regex_malloc_done(); ngx_regex_studies = NULL; -#if (NGX_PCRE2) - ngx_regex_compile_context = NULL; -#endif return NGX_OK; } @@ -732,14 +733,14 @@ ngx_regex_create_conf(ngx_cycle_t *cycle) return NULL; } - cln->handler = ngx_regex_cleanup; - cln->data = rcf; - rcf->studies = ngx_list_create(cycle->pool, 8, sizeof(ngx_regex_elt_t)); if (rcf->studies == NULL) { return NULL; } + cln->handler = ngx_regex_cleanup; + cln->data = rcf; + ngx_regex_studies = rcf->studies; return rcf; diff --git a/src/core/ngx_resolver.c b/src/core/ngx_resolver.c index 6d129e5..c76c178 100644 --- a/src/core/ngx_resolver.c +++ b/src/core/ngx_resolver.c @@ -157,6 +157,8 @@ ngx_resolver_create(ngx_conf_t *cf, ngx_str_t *names, ngx_uint_t n) cln->handler = ngx_resolver_cleanup; cln->data = r; + r->ipv4 = 1; + ngx_rbtree_init(&r->name_rbtree, &r->name_sentinel, ngx_resolver_rbtree_insert_value); @@ -225,6 +227,23 @@ ngx_resolver_create(ngx_conf_t *cf, ngx_str_t *names, ngx_uint_t n) } #if (NGX_HAVE_INET6) + if (ngx_strncmp(names[i].data, "ipv4=", 5) == 0) { + + if (ngx_strcmp(&names[i].data[5], "on") == 0) { + r->ipv4 = 1; + + } else if (ngx_strcmp(&names[i].data[5], "off") == 0) { + r->ipv4 = 0; + + } else { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid parameter: %V", &names[i]); + return NULL; + } + + continue; + } + if (ngx_strncmp(names[i].data, "ipv6=", 5) == 0) { if (ngx_strcmp(&names[i].data[5], "on") == 0) { @@ -273,6 +292,14 @@ ngx_resolver_create(ngx_conf_t *cf, ngx_str_t *names, ngx_uint_t n) } } +#if (NGX_HAVE_INET6) + if (r->ipv4 + r->ipv6 == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"ipv4\" and \"ipv6\" cannot both be \"off\""); + return NULL; + } +#endif + if (n && r->connections.nelts == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "no name servers defined"); return NULL; @@ -836,7 +863,7 @@ ngx_resolve_name_locked(ngx_resolver_t *r, ngx_resolver_ctx_t *ctx, r->last_connection = 0; } - rn->naddrs = (u_short) -1; + rn->naddrs = r->ipv4 ? (u_short) -1 : 0; rn->tcp = 0; #if (NGX_HAVE_INET6) rn->naddrs6 = r->ipv6 ? (u_short) -1 : 0; @@ -1263,7 +1290,7 @@ ngx_resolver_send_query(ngx_resolver_t *r, ngx_resolver_node_t *rn) rec->log.action = "resolving"; } - if (rn->naddrs == (u_short) -1) { + if (rn->query && rn->naddrs == (u_short) -1) { rc = rn->tcp ? ngx_resolver_send_tcp_query(r, rec, rn->query, rn->qlen) : ngx_resolver_send_udp_query(r, rec, rn->query, rn->qlen); @@ -1389,6 +1416,7 @@ ngx_resolver_send_tcp_query(ngx_resolver_t *r, ngx_resolver_connection_t *rec, rec->tcp->data = rec; rec->tcp->write->handler = ngx_resolver_tcp_write; + rec->tcp->write->cancelable = 1; rec->tcp->read->handler = ngx_resolver_tcp_read; rec->tcp->read->resolver = 1; @@ -1764,10 +1792,13 @@ ngx_resolver_process_response(ngx_resolver_t *r, u_char *buf, size_t n, q = ngx_queue_next(q)) { rn = ngx_queue_data(q, ngx_resolver_node_t, queue); - qident = (rn->query[0] << 8) + rn->query[1]; - if (qident == ident) { - goto dns_error_name; + if (rn->query) { + qident = (rn->query[0] << 8) + rn->query[1]; + + if (qident == ident) { + goto dns_error_name; + } } #if (NGX_HAVE_INET6) @@ -3644,7 +3675,7 @@ ngx_resolver_create_name_query(ngx_resolver_t *r, ngx_resolver_node_t *rn, len = sizeof(ngx_resolver_hdr_t) + nlen + sizeof(ngx_resolver_qs_t); #if (NGX_HAVE_INET6) - p = ngx_resolver_alloc(r, r->ipv6 ? len * 2 : len); + p = ngx_resolver_alloc(r, len * (r->ipv4 + r->ipv6)); #else p = ngx_resolver_alloc(r, len); #endif @@ -3657,19 +3688,21 @@ ngx_resolver_create_name_query(ngx_resolver_t *r, ngx_resolver_node_t *rn, #if (NGX_HAVE_INET6) if (r->ipv6) { - rn->query6 = p + len; + rn->query6 = r->ipv4 ? (p + len) : p; } #endif query = (ngx_resolver_hdr_t *) p; - ident = ngx_random(); + if (r->ipv4) { + ident = ngx_random(); - ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0, - "resolve: \"%V\" A %i", name, ident & 0xffff); + ngx_log_debug2(NGX_LOG_DEBUG_CORE, r->log, 0, + "resolve: \"%V\" A %i", name, ident & 0xffff); - query->ident_hi = (u_char) ((ident >> 8) & 0xff); - query->ident_lo = (u_char) (ident & 0xff); + query->ident_hi = (u_char) ((ident >> 8) & 0xff); + query->ident_lo = (u_char) (ident & 0xff); + } /* recursion query */ query->flags_hi = 1; query->flags_lo = 0; @@ -3730,7 +3763,9 @@ ngx_resolver_create_name_query(ngx_resolver_t *r, ngx_resolver_node_t *rn, p = rn->query6; - ngx_memcpy(p, rn->query, rn->qlen); + if (r->ipv4) { + ngx_memcpy(p, rn->query, rn->qlen); + } query = (ngx_resolver_hdr_t *) p; diff --git a/src/core/ngx_resolver.h b/src/core/ngx_resolver.h index 0bd3921..06026b7 100644 --- a/src/core/ngx_resolver.h +++ b/src/core/ngx_resolver.h @@ -175,8 +175,10 @@ struct ngx_resolver_s { ngx_queue_t srv_expire_queue; ngx_queue_t addr_expire_queue; + unsigned ipv4:1; + #if (NGX_HAVE_INET6) - ngx_uint_t ipv6; /* unsigned ipv6:1; */ + unsigned ipv6:1; ngx_rbtree_t addr6_rbtree; ngx_rbtree_node_t addr6_sentinel; ngx_queue_t addr6_resend_queue; diff --git a/src/core/ngx_string.c b/src/core/ngx_string.c index 98f270a..f8f7384 100644 --- a/src/core/ngx_string.c +++ b/src/core/ngx_string.c @@ -1364,7 +1364,12 @@ ngx_utf8_decode(u_char **p, size_t n) u = **p; - if (u >= 0xf0) { + if (u >= 0xf8) { + + (*p)++; + return 0xffffffff; + + } else if (u >= 0xf0) { u &= 0x07; valid = 0xffff; diff --git a/src/core/ngx_string.h b/src/core/ngx_string.h index 0fb9be7..713eb42 100644 --- a/src/core/ngx_string.h +++ b/src/core/ngx_string.h @@ -140,12 +140,12 @@ ngx_copy(u_char *dst, u_char *src, size_t len) #endif -#define ngx_memmove(dst, src, n) (void) memmove(dst, src, n) -#define ngx_movemem(dst, src, n) (((u_char *) memmove(dst, src, n)) + (n)) +#define ngx_memmove(dst, src, n) (void) memmove(dst, src, n) +#define ngx_movemem(dst, src, n) (((u_char *) memmove(dst, src, n)) + (n)) /* msvc and icc7 compile memcmp() to the inline loop */ -#define ngx_memcmp(s1, s2, n) memcmp((const char *) s1, (const char *) s2, n) +#define ngx_memcmp(s1, s2, n) memcmp(s1, s2, n) u_char *ngx_cpystrn(u_char *dst, u_char *src, size_t n); diff --git a/src/core/ngx_syslog.c b/src/core/ngx_syslog.c index 3c7b63a..bad45bd 100644 --- a/src/core/ngx_syslog.c +++ b/src/core/ngx_syslog.c @@ -18,6 +18,7 @@ static char *ngx_syslog_parse_args(ngx_conf_t *cf, ngx_syslog_peer_t *peer); static ngx_int_t ngx_syslog_init_peer(ngx_syslog_peer_t *peer); static void ngx_syslog_cleanup(void *data); +static u_char *ngx_syslog_log_error(ngx_log_t *log, u_char *buf, size_t len); static char *facilities[] = { @@ -66,6 +67,9 @@ ngx_syslog_process_conf(ngx_conf_t *cf, ngx_syslog_peer_t *peer) ngx_str_set(&peer->tag, "nginx"); } + peer->hostname = &cf->cycle->hostname; + peer->logp = &cf->cycle->new_log; + peer->conn.fd = (ngx_socket_t) -1; peer->conn.read = &ngx_syslog_dummy_event; @@ -243,7 +247,7 @@ ngx_syslog_add_header(ngx_syslog_peer_t *peer, u_char *buf) } return ngx_sprintf(buf, "<%ui>%V %V %V: ", pri, &ngx_cached_syslog_time, - &ngx_cycle->hostname, &peer->tag); + peer->hostname, &peer->tag); } @@ -286,15 +290,19 @@ ngx_syslog_send(ngx_syslog_peer_t *peer, u_char *buf, size_t len) { ssize_t n; + if (peer->log.handler == NULL) { + peer->log = *peer->logp; + peer->log.handler = ngx_syslog_log_error; + peer->log.data = peer; + peer->log.action = "logging to syslog"; + } + if (peer->conn.fd == (ngx_socket_t) -1) { if (ngx_syslog_init_peer(peer) != NGX_OK) { return NGX_ERROR; } } - /* log syslog socket events with valid log */ - peer->conn.log = ngx_cycle->log; - if (ngx_send) { n = ngx_send(&peer->conn, buf, len); @@ -306,7 +314,7 @@ ngx_syslog_send(ngx_syslog_peer_t *peer, u_char *buf, size_t len) if (n == NGX_ERROR) { if (ngx_close_socket(peer->conn.fd) == -1) { - ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno, + ngx_log_error(NGX_LOG_ALERT, &peer->log, ngx_socket_errno, ngx_close_socket_n " failed"); } @@ -324,24 +332,25 @@ ngx_syslog_init_peer(ngx_syslog_peer_t *peer) fd = ngx_socket(peer->server.sockaddr->sa_family, SOCK_DGRAM, 0); if (fd == (ngx_socket_t) -1) { - ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno, + ngx_log_error(NGX_LOG_ALERT, &peer->log, ngx_socket_errno, ngx_socket_n " failed"); return NGX_ERROR; } if (ngx_nonblocking(fd) == -1) { - ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno, + ngx_log_error(NGX_LOG_ALERT, &peer->log, ngx_socket_errno, ngx_nonblocking_n " failed"); goto failed; } if (connect(fd, peer->server.sockaddr, peer->server.socklen) == -1) { - ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno, + ngx_log_error(NGX_LOG_ALERT, &peer->log, ngx_socket_errno, "connect() failed"); goto failed; } peer->conn.fd = fd; + peer->conn.log = &peer->log; /* UDP sockets are always ready to write */ peer->conn.write->ready = 1; @@ -351,7 +360,7 @@ ngx_syslog_init_peer(ngx_syslog_peer_t *peer) failed: if (ngx_close_socket(fd) == -1) { - ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno, + ngx_log_error(NGX_LOG_ALERT, &peer->log, ngx_socket_errno, ngx_close_socket_n " failed"); } @@ -372,7 +381,30 @@ ngx_syslog_cleanup(void *data) } if (ngx_close_socket(peer->conn.fd) == -1) { - ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_socket_errno, + ngx_log_error(NGX_LOG_ALERT, &peer->log, ngx_socket_errno, ngx_close_socket_n " failed"); } } + + +static u_char * +ngx_syslog_log_error(ngx_log_t *log, u_char *buf, size_t len) +{ + u_char *p; + ngx_syslog_peer_t *peer; + + p = buf; + + if (log->action) { + p = ngx_snprintf(buf, len, " while %s", log->action); + len -= p - buf; + } + + peer = log->data; + + if (peer) { + p = ngx_snprintf(p, len, ", server: %V", &peer->server.name); + } + + return p; +} diff --git a/src/core/ngx_syslog.h b/src/core/ngx_syslog.h index 50dcd35..e2d54ac 100644 --- a/src/core/ngx_syslog.h +++ b/src/core/ngx_syslog.h @@ -9,14 +9,20 @@ typedef struct { - ngx_uint_t facility; - ngx_uint_t severity; - ngx_str_t tag; - - ngx_addr_t server; - ngx_connection_t conn; - unsigned busy:1; - unsigned nohostname:1; + ngx_uint_t facility; + ngx_uint_t severity; + ngx_str_t tag; + + ngx_str_t *hostname; + + ngx_addr_t server; + ngx_connection_t conn; + + ngx_log_t log; + ngx_log_t *logp; + + unsigned busy:1; + unsigned nohostname:1; } ngx_syslog_peer_t; diff --git a/src/event/modules/ngx_iocp_module.c b/src/event/modules/ngx_iocp_module.c new file mode 100644 index 0000000..dca5ced --- /dev/null +++ b/src/event/modules/ngx_iocp_module.c @@ -0,0 +1,379 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_iocp_module.h> + + +static ngx_int_t ngx_iocp_init(ngx_cycle_t *cycle, ngx_msec_t timer); +static ngx_thread_value_t __stdcall ngx_iocp_timer(void *data); +static void ngx_iocp_done(ngx_cycle_t *cycle); +static ngx_int_t ngx_iocp_add_event(ngx_event_t *ev, ngx_int_t event, + ngx_uint_t key); +static ngx_int_t ngx_iocp_del_connection(ngx_connection_t *c, ngx_uint_t flags); +static ngx_int_t ngx_iocp_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, + ngx_uint_t flags); +static void *ngx_iocp_create_conf(ngx_cycle_t *cycle); +static char *ngx_iocp_init_conf(ngx_cycle_t *cycle, void *conf); + + +static ngx_str_t iocp_name = ngx_string("iocp"); + +static ngx_command_t ngx_iocp_commands[] = { + + { ngx_string("iocp_threads"), + NGX_EVENT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_iocp_conf_t, threads), + NULL }, + + { ngx_string("post_acceptex"), + NGX_EVENT_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + 0, + offsetof(ngx_iocp_conf_t, post_acceptex), + NULL }, + + { ngx_string("acceptex_read"), + NGX_EVENT_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + 0, + offsetof(ngx_iocp_conf_t, acceptex_read), + NULL }, + + ngx_null_command +}; + + +static ngx_event_module_t ngx_iocp_module_ctx = { + &iocp_name, + ngx_iocp_create_conf, /* create configuration */ + ngx_iocp_init_conf, /* init configuration */ + + { + ngx_iocp_add_event, /* add an event */ + NULL, /* delete an event */ + NULL, /* enable an event */ + NULL, /* disable an event */ + NULL, /* add an connection */ + ngx_iocp_del_connection, /* delete an connection */ + NULL, /* trigger a notify */ + ngx_iocp_process_events, /* process the events */ + ngx_iocp_init, /* init the events */ + ngx_iocp_done /* done the events */ + } + +}; + +ngx_module_t ngx_iocp_module = { + NGX_MODULE_V1, + &ngx_iocp_module_ctx, /* module context */ + ngx_iocp_commands, /* module directives */ + NGX_EVENT_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +ngx_os_io_t ngx_iocp_io = { + ngx_overlapped_wsarecv, + NULL, + ngx_udp_overlapped_wsarecv, + NULL, + NULL, + NULL, + ngx_overlapped_wsasend_chain, + 0 +}; + + +static HANDLE iocp; +static ngx_tid_t timer_thread; +static ngx_msec_t msec; + + +static ngx_int_t +ngx_iocp_init(ngx_cycle_t *cycle, ngx_msec_t timer) +{ + ngx_iocp_conf_t *cf; + + cf = ngx_event_get_conf(cycle->conf_ctx, ngx_iocp_module); + + if (iocp == NULL) { + iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, + cf->threads); + } + + if (iocp == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateIoCompletionPort() failed"); + return NGX_ERROR; + } + + ngx_io = ngx_iocp_io; + + ngx_event_actions = ngx_iocp_module_ctx.actions; + + ngx_event_flags = NGX_USE_IOCP_EVENT; + + if (timer == 0) { + return NGX_OK; + } + + /* + * The waitable timer could not be used, because + * GetQueuedCompletionStatus() does not set a thread to alertable state + */ + + if (timer_thread == NULL) { + + msec = timer; + + if (ngx_create_thread(&timer_thread, ngx_iocp_timer, &msec, cycle->log) + != 0) + { + return NGX_ERROR; + } + } + + ngx_event_flags |= NGX_USE_TIMER_EVENT; + + return NGX_OK; +} + + +static ngx_thread_value_t __stdcall +ngx_iocp_timer(void *data) +{ + ngx_msec_t timer = *(ngx_msec_t *) data; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, + "THREAD %p %p", &msec, data); + + for ( ;; ) { + Sleep(timer); + + ngx_time_update(); +#if 1 + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, "timer"); +#endif + } + +#if defined(__WATCOMC__) || defined(__GNUC__) + return 0; +#endif +} + + +static void +ngx_iocp_done(ngx_cycle_t *cycle) +{ + if (CloseHandle(iocp) == -1) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "iocp CloseHandle() failed"); + } + + iocp = NULL; +} + + +static ngx_int_t +ngx_iocp_add_event(ngx_event_t *ev, ngx_int_t event, ngx_uint_t key) +{ + ngx_connection_t *c; + + c = (ngx_connection_t *) ev->data; + + c->read->active = 1; + c->write->active = 1; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "iocp add: fd:%d k:%ui ov:%p", c->fd, key, &ev->ovlp); + + if (CreateIoCompletionPort((HANDLE) c->fd, iocp, key, 0) == NULL) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "CreateIoCompletionPort() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_iocp_del_connection(ngx_connection_t *c, ngx_uint_t flags) +{ +#if 0 + if (flags & NGX_CLOSE_EVENT) { + return NGX_OK; + } + + if (CancelIo((HANDLE) c->fd) == 0) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, "CancelIo() failed"); + return NGX_ERROR; + } +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_iocp_process_events(ngx_cycle_t *cycle, ngx_msec_t timer, ngx_uint_t flags) +{ + int rc; + u_int key; + u_long bytes; + ngx_err_t err; + ngx_msec_t delta; + ngx_event_t *ev; + ngx_event_ovlp_t *ovlp; + + if (timer == NGX_TIMER_INFINITE) { + timer = INFINITE; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, "iocp timer: %M", timer); + + rc = GetQueuedCompletionStatus(iocp, &bytes, (PULONG_PTR) &key, + (LPOVERLAPPED *) &ovlp, (u_long) timer); + + if (rc == 0) { + err = ngx_errno; + } else { + err = 0; + } + + delta = ngx_current_msec; + + if (flags & NGX_UPDATE_TIME) { + ngx_time_update(); + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "iocp: %d b:%d k:%d ov:%p", rc, bytes, key, ovlp); + + if (timer != INFINITE) { + delta = ngx_current_msec - delta; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "iocp timer: %M, delta: %M", timer, delta); + } + + if (err) { + if (ovlp == NULL) { + if (err != WAIT_TIMEOUT) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, err, + "GetQueuedCompletionStatus() failed"); + + return NGX_ERROR; + } + + return NGX_OK; + } + + ovlp->error = err; + } + + if (ovlp == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "GetQueuedCompletionStatus() returned no operation"); + return NGX_ERROR; + } + + + ev = ovlp->event; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, err, "iocp event:%p", ev); + + + if (err == ERROR_NETNAME_DELETED /* the socket was closed */ + || err == ERROR_OPERATION_ABORTED /* the operation was canceled */) + { + + /* + * the WSA_OPERATION_ABORTED completion notification + * for a file descriptor that was closed + */ + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, err, + "iocp: aborted event %p", ev); + + return NGX_OK; + } + + if (err) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, err, + "GetQueuedCompletionStatus() returned operation error"); + } + + switch (key) { + + case NGX_IOCP_ACCEPT: + if (bytes) { + ev->ready = 1; + } + break; + + case NGX_IOCP_IO: + ev->complete = 1; + ev->ready = 1; + break; + + case NGX_IOCP_CONNECT: + ev->ready = 1; + } + + ev->available = bytes; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "iocp event handler: %p", ev->handler); + + ev->handler(ev); + + return NGX_OK; +} + + +static void * +ngx_iocp_create_conf(ngx_cycle_t *cycle) +{ + ngx_iocp_conf_t *cf; + + cf = ngx_palloc(cycle->pool, sizeof(ngx_iocp_conf_t)); + if (cf == NULL) { + return NULL; + } + + cf->threads = NGX_CONF_UNSET; + cf->post_acceptex = NGX_CONF_UNSET; + cf->acceptex_read = NGX_CONF_UNSET; + + return cf; +} + + +static char * +ngx_iocp_init_conf(ngx_cycle_t *cycle, void *conf) +{ + ngx_iocp_conf_t *cf = conf; + + ngx_conf_init_value(cf->threads, 0); + ngx_conf_init_value(cf->post_acceptex, 10); + ngx_conf_init_value(cf->acceptex_read, 1); + + return NGX_CONF_OK; +} diff --git a/src/event/modules/ngx_iocp_module.h b/src/event/modules/ngx_iocp_module.h new file mode 100644 index 0000000..dc73983 --- /dev/null +++ b/src/event/modules/ngx_iocp_module.h @@ -0,0 +1,22 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_IOCP_MODULE_H_INCLUDED_ +#define _NGX_IOCP_MODULE_H_INCLUDED_ + + +typedef struct { + int threads; + int post_acceptex; + int acceptex_read; +} ngx_iocp_conf_t; + + +extern ngx_module_t ngx_iocp_module; + + +#endif /* _NGX_IOCP_MODULE_H_INCLUDED_ */ diff --git a/src/event/ngx_event.c b/src/event/ngx_event.c index 47229b5..ef525d9 100644 --- a/src/event/ngx_event.c +++ b/src/event/ngx_event.c @@ -267,6 +267,18 @@ ngx_process_events_and_timers(ngx_cycle_t *cycle) ngx_int_t ngx_handle_read_event(ngx_event_t *rev, ngx_uint_t flags) { +#if (NGX_QUIC) + + ngx_connection_t *c; + + c = rev->data; + + if (c->quic) { + return NGX_OK; + } + +#endif + if (ngx_event_flags & NGX_USE_CLEAR_EVENT) { /* kqueue, epoll */ @@ -337,9 +349,15 @@ ngx_handle_write_event(ngx_event_t *wev, size_t lowat) { ngx_connection_t *c; - if (lowat) { - c = wev->data; + c = wev->data; +#if (NGX_QUIC) + if (c->quic) { + return NGX_OK; + } +#endif + + if (lowat) { if (ngx_send_lowat(c, lowat) == NGX_ERROR) { return NGX_ERROR; } @@ -416,6 +434,7 @@ ngx_event_init_conf(ngx_cycle_t *cycle, void *conf) { #if (NGX_HAVE_REUSEPORT) ngx_uint_t i; + ngx_core_conf_t *ccf; ngx_listening_t *ls; #endif @@ -442,7 +461,9 @@ ngx_event_init_conf(ngx_cycle_t *cycle, void *conf) #if (NGX_HAVE_REUSEPORT) - if (!ngx_test_config) { + ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); + + if (!ngx_test_config && ccf->master) { ls = cycle->listening.elts; for (i = 0; i < cycle->listening.nelts; i++) { @@ -810,7 +831,9 @@ ngx_event_process_init(ngx_cycle_t *cycle) rev->deferred_accept = ls[i].deferred_accept; #endif - if (!(ngx_event_flags & NGX_USE_IOCP_EVENT)) { + if (!(ngx_event_flags & NGX_USE_IOCP_EVENT) + && cycle->old_cycle) + { if (ls[i].previous) { /* @@ -868,8 +891,16 @@ ngx_event_process_init(ngx_cycle_t *cycle) #else - rev->handler = (c->type == SOCK_STREAM) ? ngx_event_accept - : ngx_event_recvmsg; + if (c->type == SOCK_STREAM) { + rev->handler = ngx_event_accept; + +#if (NGX_QUIC) + } else if (ls[i].quic) { + rev->handler = ngx_quic_recvmsg; +#endif + } else { + rev->handler = ngx_event_recvmsg; + } #if (NGX_HAVE_REUSEPORT) diff --git a/src/event/ngx_event.h b/src/event/ngx_event.h index 548c906..deac04e 100644 --- a/src/event/ngx_event.h +++ b/src/event/ngx_event.h @@ -494,12 +494,6 @@ extern ngx_module_t ngx_event_core_module; void ngx_event_accept(ngx_event_t *ev); -#if !(NGX_WIN32) -void ngx_event_recvmsg(ngx_event_t *ev); -void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, - ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); -#endif -void ngx_delete_udp_connection(void *data); ngx_int_t ngx_trylock_accept_mutex(ngx_cycle_t *cycle); ngx_int_t ngx_enable_accept_events(ngx_cycle_t *cycle); u_char *ngx_accept_log_error(ngx_log_t *log, u_char *buf, size_t len); @@ -529,6 +523,7 @@ ngx_int_t ngx_send_lowat(ngx_connection_t *c, size_t lowat); #include <ngx_event_timer.h> #include <ngx_event_posted.h> +#include <ngx_event_udp.h> #if (NGX_WIN32) #include <ngx_iocp_module.h> diff --git a/src/event/ngx_event_acceptex.c b/src/event/ngx_event_acceptex.c new file mode 100644 index 0000000..f4a1c4b --- /dev/null +++ b/src/event/ngx_event_acceptex.c @@ -0,0 +1,229 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> + + +static void ngx_close_posted_connection(ngx_connection_t *c); + + +void +ngx_event_acceptex(ngx_event_t *rev) +{ + ngx_listening_t *ls; + ngx_connection_t *c; + + c = rev->data; + ls = c->listening; + + c->log->handler = ngx_accept_log_error; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "AcceptEx: %d", c->fd); + + if (rev->ovlp.error) { + ngx_log_error(NGX_LOG_CRIT, c->log, rev->ovlp.error, + "AcceptEx() %V failed", &ls->addr_text); + return; + } + + /* SO_UPDATE_ACCEPT_CONTEXT is required for shutdown() to work */ + + if (setsockopt(c->fd, SOL_SOCKET, SO_UPDATE_ACCEPT_CONTEXT, + (char *) &ls->fd, sizeof(ngx_socket_t)) + == -1) + { + ngx_log_error(NGX_LOG_CRIT, c->log, ngx_socket_errno, + "setsockopt(SO_UPDATE_ACCEPT_CONTEXT) failed for %V", + &c->addr_text); + /* TODO: close socket */ + return; + } + + ngx_getacceptexsockaddrs(c->buffer->pos, + ls->post_accept_buffer_size, + ls->socklen + 16, + ls->socklen + 16, + &c->local_sockaddr, &c->local_socklen, + &c->sockaddr, &c->socklen); + + if (ls->post_accept_buffer_size) { + c->buffer->last += rev->available; + c->buffer->end = c->buffer->start + ls->post_accept_buffer_size; + + } else { + c->buffer = NULL; + } + + if (ls->addr_ntop) { + c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len); + if (c->addr_text.data == NULL) { + /* TODO: close socket */ + return; + } + + c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen, + c->addr_text.data, + ls->addr_text_max_len, 0); + if (c->addr_text.len == 0) { + /* TODO: close socket */ + return; + } + } + + ngx_event_post_acceptex(ls, 1); + + c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + + c->start_time = ngx_current_msec; + + ls->handler(c); + + return; + +} + + +ngx_int_t +ngx_event_post_acceptex(ngx_listening_t *ls, ngx_uint_t n) +{ + u_long rcvd; + ngx_err_t err; + ngx_log_t *log; + ngx_uint_t i; + ngx_event_t *rev, *wev; + ngx_socket_t s; + ngx_connection_t *c; + + for (i = 0; i < n; i++) { + + /* TODO: look up reused sockets */ + + s = ngx_socket(ls->sockaddr->sa_family, ls->type, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, &ls->log, 0, + ngx_socket_n " s:%d", s); + + if (s == (ngx_socket_t) -1) { + ngx_log_error(NGX_LOG_ALERT, &ls->log, ngx_socket_errno, + ngx_socket_n " failed"); + + return NGX_ERROR; + } + + c = ngx_get_connection(s, &ls->log); + + if (c == NULL) { + return NGX_ERROR; + } + + c->pool = ngx_create_pool(ls->pool_size, &ls->log); + if (c->pool == NULL) { + ngx_close_posted_connection(c); + return NGX_ERROR; + } + + log = ngx_palloc(c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + ngx_close_posted_connection(c); + return NGX_ERROR; + } + + c->buffer = ngx_create_temp_buf(c->pool, ls->post_accept_buffer_size + + 2 * (ls->socklen + 16)); + if (c->buffer == NULL) { + ngx_close_posted_connection(c); + return NGX_ERROR; + } + + c->local_sockaddr = ngx_palloc(c->pool, ls->socklen); + if (c->local_sockaddr == NULL) { + ngx_close_posted_connection(c); + return NGX_ERROR; + } + + c->sockaddr = ngx_palloc(c->pool, ls->socklen); + if (c->sockaddr == NULL) { + ngx_close_posted_connection(c); + return NGX_ERROR; + } + + *log = ls->log; + c->log = log; + + c->recv = ngx_recv; + c->send = ngx_send; + c->recv_chain = ngx_recv_chain; + c->send_chain = ngx_send_chain; + + c->listening = ls; + + rev = c->read; + wev = c->write; + + rev->ovlp.event = rev; + wev->ovlp.event = wev; + rev->handler = ngx_event_acceptex; + + rev->ready = 1; + wev->ready = 1; + + rev->log = c->log; + wev->log = c->log; + + if (ngx_add_event(rev, 0, NGX_IOCP_IO) == NGX_ERROR) { + ngx_close_posted_connection(c); + return NGX_ERROR; + } + + if (ngx_acceptex(ls->fd, s, c->buffer->pos, ls->post_accept_buffer_size, + ls->socklen + 16, ls->socklen + 16, + &rcvd, (LPOVERLAPPED) &rev->ovlp) + == 0) + { + err = ngx_socket_errno; + if (err != WSA_IO_PENDING) { + ngx_log_error(NGX_LOG_ALERT, &ls->log, err, + "AcceptEx() %V failed", &ls->addr_text); + + ngx_close_posted_connection(c); + return NGX_ERROR; + } + } + } + + return NGX_OK; +} + + +static void +ngx_close_posted_connection(ngx_connection_t *c) +{ + ngx_socket_t fd; + + ngx_free_connection(c); + + fd = c->fd; + c->fd = (ngx_socket_t) -1; + + if (ngx_close_socket(fd) == -1) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno, + ngx_close_socket_n " failed"); + } + + if (c->pool) { + ngx_destroy_pool(c->pool); + } +} + + +u_char * +ngx_acceptex_log_error(ngx_log_t *log, u_char *buf, size_t len) +{ + return ngx_snprintf(buf, len, " while posting AcceptEx() on %V", log->data); +} diff --git a/src/event/ngx_event_connect.c b/src/event/ngx_event_connect.c index adbbde6..668084a 100644 --- a/src/event/ngx_event_connect.c +++ b/src/event/ngx_event_connect.c @@ -179,6 +179,8 @@ ngx_event_connect_peer(ngx_peer_connection_t *pc) c->recv = ngx_udp_recv; c->send = ngx_send; c->send_chain = ngx_udp_send_chain; + + c->need_flush_buf = 1; } c->log_error = pc->log_error; diff --git a/src/event/ngx_event_connectex.c b/src/event/ngx_event_connectex.c new file mode 100644 index 0000000..36b1514 --- /dev/null +++ b/src/event/ngx_event_connectex.c @@ -0,0 +1,206 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> + + +#define NGX_MAX_PENDING_CONN 10 + + +static CRITICAL_SECTION connect_lock; +static int nconnects; +static ngx_connection_t pending_connects[NGX_MAX_PENDING_CONN]; + +static HANDLE pending_connect_event; + +__declspec(thread) int nevents = 0; +__declspec(thread) WSAEVENT events[WSA_MAXIMUM_WAIT_EVENTS + 1]; +__declspec(thread) ngx_connection_t *conn[WSA_MAXIMUM_WAIT_EVENTS + 1]; + + + +int ngx_iocp_wait_connect(ngx_connection_t *c) +{ + for ( ;; ) { + EnterCriticalSection(&connect_lock); + + if (nconnects < NGX_MAX_PENDING_CONN) { + pending_connects[--nconnects] = c; + LeaveCriticalSection(&connect_lock); + + if (SetEvent(pending_connect_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "SetEvent() failed"); + return NGX_ERROR; + + break; + } + + LeaveCriticalSection(&connect_lock); + ngx_log_error(NGX_LOG_NOTICE, c->log, 0, + "max number of pending connect()s is %d", + NGX_MAX_PENDING_CONN); + msleep(100); + } + + if (!started) { + if (ngx_iocp_new_thread(1) == NGX_ERROR) { + return NGX_ERROR; + } + started = 1; + } + + return NGX_OK; +} + + +int ngx_iocp_new_thread(int main) +{ + u_int id; + + if (main) { + pending_connect_event = CreateEvent(NULL, 0, 1, NULL); + if (pending_connect_event == INVALID_HANDLE_VALUE) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "CreateThread() failed"); + return NGX_ERROR; + } + } + + if (CreateThread(NULL, 0, ngx_iocp_wait_events, main, 0, &id) + == INVALID_HANDLE_VALUE) + { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "CreateThread() failed"); + return NGX_ERROR; + } + + SetEvent(event) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_errno, + "SetEvent() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +int ngx_iocp_new_connect() +{ + EnterCriticalSection(&connect_lock); + c = pending_connects[--nconnects]; + LeaveCriticalSection(&connect_lock); + + conn[nevents] = c; + + events[nevents] = WSACreateEvent(); + if (events[nevents] == INVALID_HANDLE_VALUE) { + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno, + "WSACreateEvent() failed"); + return NGX_ERROR; + } + + if (WSAEventSelect(c->fd, events[nevents], FD_CONNECT) == -1) + ngx_log_error(NGX_LOG_ALERT, c->log, ngx_socket_errno, + "WSAEventSelect() failed"); + return NGX_ERROR; + } + + nevents++; + + return NGX_OK; +} + + +void ngx_iocp_wait_events(int main) +{ + WSANETWORKEVENTS ne; + + nevents = 1; + events[0] = pending_connect_event; + conn[0] = NULL; + + for ( ;; ) { + offset = (nevents == WSA_MAXIMUM_WAIT_EVENTS + 1) ? 1 : 0; + timeout = (nevents == 1 && !first) ? 60000 : INFINITE; + + n = WSAWaitForMultipleEvents(nevents - offset, events[offset], + 0, timeout, 0); + if (n == WAIT_FAILED) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno, + "WSAWaitForMultipleEvents() failed"); + continue; + } + + if (n == WAIT_TIMEOUT) { + if (nevents == 2 && !main) { + ExitThread(0); + } + + ngx_log_error(NGX_LOG_ALERT, log, 0, + "WSAWaitForMultipleEvents() " + "returned unexpected WAIT_TIMEOUT"); + continue; + } + + n -= WSA_WAIT_EVENT_0; + + if (events[n] == NULL) { + + /* the pending_connect_event */ + + if (nevents == WSA_MAXIMUM_WAIT_EVENTS) { + ngx_iocp_new_thread(0); + } else { + ngx_iocp_new_connect(); + } + + continue; + } + + if (WSAEnumNetworkEvents(c[n].fd, events[n], &ne) == -1) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno, + "WSAEnumNetworkEvents() failed"); + continue; + } + + if (ne.lNetworkEvents & FD_CONNECT) { + conn[n].write->ovlp.error = ne.iErrorCode[FD_CONNECT_BIT]; + + if (PostQueuedCompletionStatus(iocp, 0, NGX_IOCP_CONNECT, + &conn[n].write->ovlp) == 0) + { + ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno, + "PostQueuedCompletionStatus() failed"); + continue; + } + + if (n < nevents) { + conn[n] = conn[nevents]; + events[n] = events[nevents]; + } + + nevents--; + continue; + } + + if (ne.lNetworkEvents & FD_ACCEPT) { + + /* CHECK ERROR ??? */ + + ngx_event_post_acceptex(conn[n].listening, 1); + continue; + } + + ngx_log_error(NGX_LOG_ALERT, c[n].log, 0, + "WSAWaitForMultipleEvents() " + "returned unexpected network event %ul", + ne.lNetworkEvents); + } +} diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 1e6fc96..89f277f 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -33,9 +33,6 @@ static int ngx_ssl_new_client_session(ngx_ssl_conn_t *ssl_conn, #ifdef SSL_READ_EARLY_DATA_SUCCESS static ngx_int_t ngx_ssl_try_early_data(ngx_connection_t *c); #endif -#if (NGX_DEBUG) -static void ngx_ssl_handshake_log(ngx_connection_t *c); -#endif static void ngx_ssl_handshake_handler(ngx_event_t *ev); #ifdef SSL_READ_EARLY_DATA_SUCCESS static ssize_t ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf, @@ -71,10 +68,11 @@ static void ngx_ssl_session_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); #ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB -static int ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, +static int ngx_ssl_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc); -static void ngx_ssl_session_ticket_keys_cleanup(void *data); +static ngx_int_t ngx_ssl_rotate_ticket_keys(SSL_CTX *ssl_ctx, ngx_log_t *log); +static void ngx_ssl_ticket_keys_cleanup(void *data); #endif #ifndef X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT @@ -131,7 +129,7 @@ ngx_module_t ngx_openssl_module = { int ngx_ssl_connection_index; int ngx_ssl_server_conf_index; int ngx_ssl_session_cache_index; -int ngx_ssl_session_ticket_keys_index; +int ngx_ssl_ticket_keys_index; int ngx_ssl_ocsp_index; int ngx_ssl_certificate_index; int ngx_ssl_next_certificate_index; @@ -142,13 +140,42 @@ int ngx_ssl_stapling_index; ngx_int_t ngx_ssl_init(ngx_log_t *log) { -#if OPENSSL_VERSION_NUMBER >= 0x10100003L +#if (OPENSSL_INIT_LOAD_CONFIG && !defined LIBRESSL_VERSION_NUMBER) + + uint64_t opts; + OPENSSL_INIT_SETTINGS *init; + + opts = OPENSSL_INIT_LOAD_CONFIG; + +#if (NGX_OPENSSL_NO_CONFIG) + + if (getenv("OPENSSL_CONF") == NULL) { + opts = OPENSSL_INIT_NO_LOAD_CONFIG; + } + +#endif + + init = OPENSSL_INIT_new(); + if (init == NULL) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, "OPENSSL_INIT_new() failed"); + return NGX_ERROR; + } + +#ifndef OPENSSL_NO_STDIO + if (OPENSSL_INIT_set_config_appname(init, "nginx") == 0) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, + "OPENSSL_INIT_set_config_appname() failed"); + return NGX_ERROR; + } +#endif - if (OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL) == 0) { + if (OPENSSL_init_ssl(opts, init) == 0) { ngx_ssl_error(NGX_LOG_ALERT, log, 0, "OPENSSL_init_ssl() failed"); return NGX_ERROR; } + OPENSSL_INIT_free(init); + /* * OPENSSL_init_ssl() may leave errors in the error queue * while returning success @@ -158,7 +185,15 @@ ngx_ssl_init(ngx_log_t *log) #else - OPENSSL_config(NULL); +#if (NGX_OPENSSL_NO_CONFIG) + + if (getenv("OPENSSL_CONF") == NULL) { + OPENSSL_no_config(); + } + +#endif + + OPENSSL_config("nginx"); SSL_library_init(); SSL_load_error_strings(); @@ -208,9 +243,9 @@ ngx_ssl_init(ngx_log_t *log) return NGX_ERROR; } - ngx_ssl_session_ticket_keys_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, - NULL, NULL); - if (ngx_ssl_session_ticket_keys_index == -1) { + ngx_ssl_ticket_keys_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL, + NULL); + if (ngx_ssl_ticket_keys_index == -1) { ngx_ssl_error(NGX_LOG_ALERT, log, 0, "SSL_CTX_get_ex_new_index() failed"); return NGX_ERROR; @@ -1070,7 +1105,8 @@ ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret) BIO *rbio, *wbio; ngx_connection_t *c; -#ifndef SSL_OP_NO_RENEGOTIATION +#if (!defined SSL_OP_NO_RENEGOTIATION \ + && !defined SSL_OP_NO_CLIENT_RENEGOTIATION) if ((where & SSL_CB_HANDSHAKE_START) && SSL_is_server((ngx_ssl_conn_t *) ssl_conn)) @@ -1083,6 +1119,53 @@ ngx_ssl_info_callback(const ngx_ssl_conn_t *ssl_conn, int where, int ret) } } +#endif + +#ifdef TLS1_3_VERSION + + if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP + && SSL_version(ssl_conn) == TLS1_3_VERSION) + { + time_t now, time, timeout, conf_timeout; + SSL_SESSION *sess; + + /* + * OpenSSL with TLSv1.3 updates the session creation time on + * session resumption and keeps the session timeout unmodified, + * making it possible to maintain the session forever, bypassing + * client certificate expiration and revocation. To make sure + * session timeouts are actually used, we now update the session + * creation time and reduce the session timeout accordingly. + * + * BoringSSL with TLSv1.3 ignores configured session timeouts + * and uses a hardcoded timeout instead, 7 days. So we update + * session timeout to the configured value as soon as a session + * is created. + */ + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + sess = SSL_get0_session(ssl_conn); + + if (!c->ssl->session_timeout_set && sess) { + c->ssl->session_timeout_set = 1; + + now = ngx_time(); + time = SSL_SESSION_get_time(sess); + timeout = SSL_SESSION_get_timeout(sess); + conf_timeout = SSL_CTX_get_timeout(c->ssl->session_ctx); + + timeout = ngx_min(timeout, conf_timeout); + + if (now - time >= timeout) { + SSL_SESSION_set1_id_context(sess, (unsigned char *) "", 0); + + } else { + SSL_SESSION_set_time(sess, now); + SSL_SESSION_set_timeout(sess, timeout - (now - time)); + } + } + } + #endif if ((where & SSL_CB_ACCEPT_LOOP) == SSL_CB_ACCEPT_LOOP) { @@ -1426,9 +1509,9 @@ ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name) SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_ECDH_USE); -#if SSL_CTRL_SET_ECDH_AUTO +#ifdef SSL_CTRL_SET_ECDH_AUTO /* not needed in OpenSSL 1.1.0+ */ - SSL_CTX_set_ecdh_auto(ssl->ctx, 1); + (void) SSL_CTX_set_ecdh_auto(ssl->ctx, 1); #endif if (ngx_strcmp(name->data, "auto") == 0) { @@ -1756,9 +1839,10 @@ ngx_ssl_handshake(ngx_connection_t *c) c->read->ready = 1; c->write->ready = 1; -#ifndef SSL_OP_NO_RENEGOTIATION -#if OPENSSL_VERSION_NUMBER < 0x10100000L -#ifdef SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS +#if (!defined SSL_OP_NO_RENEGOTIATION \ + && !defined SSL_OP_NO_CLIENT_RENEGOTIATION \ + && defined SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS \ + && OPENSSL_VERSION_NUMBER < 0x10100000L) /* initial handshake done, disable renegotiation (CVE-2009-3555) */ if (c->ssl->connection->s3 && SSL_is_server(c->ssl->connection)) { @@ -1766,10 +1850,8 @@ ngx_ssl_handshake(ngx_connection_t *c) } #endif -#endif -#endif -#ifdef BIO_get_ktls_send +#if (defined BIO_get_ktls_send && !NGX_WIN32) if (BIO_get_ktls_send(SSL_get_wbio(c->ssl->connection)) == 1) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -1914,7 +1996,7 @@ ngx_ssl_try_early_data(ngx_connection_t *c) c->read->ready = 1; c->write->ready = 1; -#ifdef BIO_get_ktls_send +#if (defined BIO_get_ktls_send && !NGX_WIN32) if (BIO_get_ktls_send(SSL_get_wbio(c->ssl->connection)) == 1) { ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, @@ -2004,7 +2086,7 @@ ngx_ssl_try_early_data(ngx_connection_t *c) #if (NGX_DEBUG) -static void +void ngx_ssl_handshake_log(ngx_connection_t *c) { char buf[129], *s, *d; @@ -2156,6 +2238,7 @@ ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size) #endif if (c->ssl->last == NGX_ERROR) { + c->read->ready = 0; c->read->error = 1; return NGX_ERROR; } @@ -2222,6 +2305,7 @@ ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size) #if (NGX_HAVE_FIONREAD) if (ngx_socket_nread(c->fd, &c->read->available) == -1) { + c->read->ready = 0; c->read->error = 1; ngx_connection_error(c, ngx_socket_errno, ngx_socket_nread_n " failed"); @@ -2258,6 +2342,7 @@ ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size) return 0; case NGX_ERROR: + c->read->ready = 0; c->read->error = 1; /* fall through */ @@ -2278,6 +2363,7 @@ ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf, size_t size) size_t readbytes; if (c->ssl->last == NGX_ERROR) { + c->read->ready = 0; c->read->error = 1; return NGX_ERROR; } @@ -2377,6 +2463,7 @@ ngx_ssl_recv_early(ngx_connection_t *c, u_char *buf, size_t size) return 0; case NGX_ERROR: + c->read->ready = 0; c->read->error = 1; /* fall through */ @@ -2396,7 +2483,8 @@ ngx_ssl_handle_recv(ngx_connection_t *c, int n) int sslerr; ngx_err_t err; -#ifndef SSL_OP_NO_RENEGOTIATION +#if (!defined SSL_OP_NO_RENEGOTIATION \ + && !defined SSL_OP_NO_CLIENT_RENEGOTIATION) if (c->ssl->renegotiation) { /* @@ -2943,7 +3031,7 @@ ngx_ssl_write_early(ngx_connection_t *c, u_char *data, size_t size) static ssize_t ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size) { -#ifdef BIO_get_ktls_send +#if (defined BIO_get_ktls_send && !NGX_WIN32) int sslerr, flags; ssize_t n; @@ -2972,7 +3060,7 @@ ngx_ssl_sendfile(ngx_connection_t *c, ngx_buf_t *file, size_t size) n = SSL_sendfile(c->ssl->connection, file->file->fd, file->file_pos, size, flags); - ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_sendfile: %d", n); + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_sendfile: %z", n); if (n > 0) { @@ -3149,6 +3237,13 @@ ngx_ssl_shutdown(ngx_connection_t *c) ngx_err_t err; ngx_uint_t tries; +#if (NGX_QUIC) + if (c->quic) { + /* QUIC streams inherit SSL object */ + return NGX_OK; + } +#endif + rc = NGX_OK; ngx_ssl_ocsp_cleanup(c); @@ -3336,30 +3431,74 @@ ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err, } else if (sslerr == SSL_ERROR_SSL) { - n = ERR_GET_REASON(ERR_peek_error()); + n = ERR_GET_REASON(ERR_peek_last_error()); /* handshake failures */ if (n == SSL_R_BAD_CHANGE_CIPHER_SPEC /* 103 */ #ifdef SSL_R_NO_SUITABLE_KEY_SHARE || n == SSL_R_NO_SUITABLE_KEY_SHARE /* 101 */ #endif +#ifdef SSL_R_BAD_ALERT + || n == SSL_R_BAD_ALERT /* 102 */ +#endif +#ifdef SSL_R_BAD_KEY_SHARE + || n == SSL_R_BAD_KEY_SHARE /* 108 */ +#endif +#ifdef SSL_R_BAD_EXTENSION + || n == SSL_R_BAD_EXTENSION /* 110 */ +#endif + || n == SSL_R_BAD_DIGEST_LENGTH /* 111 */ +#ifdef SSL_R_MISSING_SIGALGS_EXTENSION + || n == SSL_R_MISSING_SIGALGS_EXTENSION /* 112 */ +#endif + || n == SSL_R_BAD_PACKET_LENGTH /* 115 */ #ifdef SSL_R_NO_SUITABLE_SIGNATURE_ALGORITHM || n == SSL_R_NO_SUITABLE_SIGNATURE_ALGORITHM /* 118 */ +#endif +#ifdef SSL_R_BAD_KEY_UPDATE + || n == SSL_R_BAD_KEY_UPDATE /* 122 */ #endif || n == SSL_R_BLOCK_CIPHER_PAD_IS_WRONG /* 129 */ + || n == SSL_R_CCS_RECEIVED_EARLY /* 133 */ +#ifdef SSL_R_DECODE_ERROR + || n == SSL_R_DECODE_ERROR /* 137 */ +#endif +#ifdef SSL_R_DATA_BETWEEN_CCS_AND_FINISHED + || n == SSL_R_DATA_BETWEEN_CCS_AND_FINISHED /* 145 */ +#endif + || n == SSL_R_DATA_LENGTH_TOO_LONG /* 146 */ || n == SSL_R_DIGEST_CHECK_FAILED /* 149 */ + || n == SSL_R_ENCRYPTED_LENGTH_TOO_LONG /* 150 */ || n == SSL_R_ERROR_IN_RECEIVED_CIPHER_LIST /* 151 */ || n == SSL_R_EXCESSIVE_MESSAGE_SIZE /* 152 */ +#ifdef SSL_R_GOT_A_FIN_BEFORE_A_CCS + || n == SSL_R_GOT_A_FIN_BEFORE_A_CCS /* 154 */ +#endif || n == SSL_R_HTTPS_PROXY_REQUEST /* 155 */ || n == SSL_R_HTTP_REQUEST /* 156 */ || n == SSL_R_LENGTH_MISMATCH /* 159 */ +#ifdef SSL_R_LENGTH_TOO_SHORT + || n == SSL_R_LENGTH_TOO_SHORT /* 160 */ +#endif +#ifdef SSL_R_NO_RENEGOTIATION + || n == SSL_R_NO_RENEGOTIATION /* 182 */ +#endif #ifdef SSL_R_NO_CIPHERS_PASSED || n == SSL_R_NO_CIPHERS_PASSED /* 182 */ #endif || n == SSL_R_NO_CIPHERS_SPECIFIED /* 183 */ +#ifdef SSL_R_BAD_CIPHER + || n == SSL_R_BAD_CIPHER /* 186 */ +#endif || n == SSL_R_NO_COMPRESSION_SPECIFIED /* 187 */ || n == SSL_R_NO_SHARED_CIPHER /* 193 */ +#ifdef SSL_R_PACKET_LENGTH_TOO_LONG + || n == SSL_R_PACKET_LENGTH_TOO_LONG /* 198 */ +#endif || n == SSL_R_RECORD_LENGTH_MISMATCH /* 213 */ +#ifdef SSL_R_TOO_MANY_WARNING_ALERTS + || n == SSL_R_TOO_MANY_WARNING_ALERTS /* 220 */ +#endif #ifdef SSL_R_CLIENTHELLO_TLSEXT || n == SSL_R_CLIENTHELLO_TLSEXT /* 226 */ #endif @@ -3369,6 +3508,9 @@ ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err, #ifdef SSL_R_CALLBACK_FAILED || n == SSL_R_CALLBACK_FAILED /* 234 */ #endif +#ifdef SSL_R_TLS_RSA_ENCRYPTED_VALUE_LENGTH_IS_WRONG + || n == SSL_R_TLS_RSA_ENCRYPTED_VALUE_LENGTH_IS_WRONG /* 234 */ +#endif #ifdef SSL_R_NO_APPLICATION_PROTOCOL || n == SSL_R_NO_APPLICATION_PROTOCOL /* 235 */ #endif @@ -3378,13 +3520,44 @@ ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err, || n == SSL_R_UNKNOWN_PROTOCOL /* 252 */ #ifdef SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS || n == SSL_R_NO_COMMON_SIGNATURE_ALGORITHMS /* 253 */ +#endif +#ifdef SSL_R_INVALID_COMPRESSION_LIST + || n == SSL_R_INVALID_COMPRESSION_LIST /* 256 */ +#endif +#ifdef SSL_R_MISSING_KEY_SHARE + || n == SSL_R_MISSING_KEY_SHARE /* 258 */ #endif || n == SSL_R_UNSUPPORTED_PROTOCOL /* 258 */ #ifdef SSL_R_NO_SHARED_GROUP || n == SSL_R_NO_SHARED_GROUP /* 266 */ #endif || n == SSL_R_WRONG_VERSION_NUMBER /* 267 */ +#ifdef SSL_R_TOO_MUCH_SKIPPED_EARLY_DATA + || n == SSL_R_TOO_MUCH_SKIPPED_EARLY_DATA /* 270 */ +#endif + || n == SSL_R_BAD_LENGTH /* 271 */ || n == SSL_R_DECRYPTION_FAILED_OR_BAD_RECORD_MAC /* 281 */ +#ifdef SSL_R_APPLICATION_DATA_AFTER_CLOSE_NOTIFY + || n == SSL_R_APPLICATION_DATA_AFTER_CLOSE_NOTIFY /* 291 */ +#endif +#ifdef SSL_R_APPLICATION_DATA_ON_SHUTDOWN + || n == SSL_R_APPLICATION_DATA_ON_SHUTDOWN /* 291 */ +#endif +#ifdef SSL_R_BAD_LEGACY_VERSION + || n == SSL_R_BAD_LEGACY_VERSION /* 292 */ +#endif +#ifdef SSL_R_MIXED_HANDSHAKE_AND_NON_HANDSHAKE_DATA + || n == SSL_R_MIXED_HANDSHAKE_AND_NON_HANDSHAKE_DATA /* 293 */ +#endif +#ifdef SSL_R_RECORD_TOO_SMALL + || n == SSL_R_RECORD_TOO_SMALL /* 298 */ +#endif +#ifdef SSL_R_SSL3_SESSION_ID_TOO_LONG + || n == SSL_R_SSL3_SESSION_ID_TOO_LONG /* 300 */ +#endif +#ifdef SSL_R_BAD_ECPOINT + || n == SSL_R_BAD_ECPOINT /* 306 */ +#endif #ifdef SSL_R_RENEGOTIATE_EXT_TOO_LONG || n == SSL_R_RENEGOTIATE_EXT_TOO_LONG /* 335 */ || n == SSL_R_RENEGOTIATION_ENCODING_ERR /* 336 */ @@ -3399,11 +3572,23 @@ ngx_ssl_connection_error(ngx_connection_t *c, int sslerr, ngx_err_t err, #ifdef SSL_R_INAPPROPRIATE_FALLBACK || n == SSL_R_INAPPROPRIATE_FALLBACK /* 373 */ #endif +#ifdef SSL_R_NO_SHARED_SIGNATURE_ALGORITHMS + || n == SSL_R_NO_SHARED_SIGNATURE_ALGORITHMS /* 376 */ +#endif +#ifdef SSL_R_NO_SHARED_SIGATURE_ALGORITHMS + || n == SSL_R_NO_SHARED_SIGATURE_ALGORITHMS /* 376 */ +#endif #ifdef SSL_R_CERT_CB_ERROR || n == SSL_R_CERT_CB_ERROR /* 377 */ #endif #ifdef SSL_R_VERSION_TOO_LOW || n == SSL_R_VERSION_TOO_LOW /* 396 */ +#endif +#ifdef SSL_R_TOO_MANY_WARN_ALERTS + || n == SSL_R_TOO_MANY_WARN_ALERTS /* 409 */ +#endif +#ifdef SSL_R_BAD_RECORD_TYPE + || n == SSL_R_BAD_RECORD_TYPE /* 443 */ #endif || n == 1000 /* SSL_R_SSLV3_ALERT_CLOSE_NOTIFY */ #ifdef SSL_R_SSLV3_ALERT_UNEXPECTED_MESSAGE @@ -3749,6 +3934,12 @@ ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data) ngx_queue_init(&cache->expire_queue); + cache->ticket_keys[0].expire = 0; + cache->ticket_keys[1].expire = 0; + cache->ticket_keys[2].expire = 0; + + cache->fail_time = 0; + len = sizeof(" in SSL session shared cache \"\"") + shm_zone->shm.name.len; shpool->log_ctx = ngx_slab_alloc(shpool, len); @@ -3767,16 +3958,16 @@ ngx_ssl_session_cache_init(ngx_shm_zone_t *shm_zone, void *data) /* * The length of the session id is 16 bytes for SSLv2 sessions and - * between 1 and 32 bytes for SSLv3/TLSv1, typically 32 bytes. - * It seems that the typical length of the external ASN1 representation - * of a session is 118 or 119 bytes for SSLv3/TSLv1. + * between 1 and 32 bytes for SSLv3 and TLS, typically 32 bytes. + * Typical length of the external ASN1 representation of a session + * is about 150 bytes plus SNI server name. * - * Thus on 32-bit platforms we allocate separately an rbtree node, - * a session id, and an ASN1 representation, they take accordingly - * 64, 32, and 128 bytes. + * On 32-bit platforms we allocate an rbtree node, a session id, and + * an ASN1 representation in a single allocation, it typically takes + * 256 bytes. * * On 64-bit platforms we allocate separately an rbtree node + session_id, - * and an ASN1 representation, they take accordingly 128 and 128 bytes. + * and an ASN1 representation, they take accordingly 128 and 256 bytes. * * OpenSSL's i2d_SSL_SESSION() and d2i_SSL_SESSION are slow, * so they are outside the code locked by shared pool mutex @@ -3786,7 +3977,8 @@ static int ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) { int len; - u_char *p, *id, *cached_sess, *session_id; + u_char *p, *session_id; + size_t n; uint32_t hash; SSL_CTX *ssl_ctx; unsigned int session_id_length; @@ -3797,17 +3989,42 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) ngx_ssl_session_cache_t *cache; u_char buf[NGX_SSL_MAX_SESSION_SIZE]; +#ifdef TLS1_3_VERSION + + /* + * OpenSSL tries to save TLSv1.3 sessions into session cache + * even when using tickets for stateless session resumption, + * "because some applications just want to know about the creation + * of a session"; do not cache such sessions + */ + + if (SSL_version(ssl_conn) == TLS1_3_VERSION + && (SSL_get_options(ssl_conn) & SSL_OP_NO_TICKET) == 0) + { + return 0; + } + +#endif + len = i2d_SSL_SESSION(sess, NULL); /* do not cache too big session */ - if (len > (int) NGX_SSL_MAX_SESSION_SIZE) { + if (len > NGX_SSL_MAX_SESSION_SIZE) { return 0; } p = buf; i2d_SSL_SESSION(sess, &p); + session_id = (u_char *) SSL_SESSION_get_id(sess, &session_id_length); + + /* do not cache sessions with too long session id */ + + if (session_id_length > 32) { + return 0; + } + c = ngx_ssl_get_connection(ssl_conn); ssl_ctx = c->ssl->session_ctx; @@ -3821,23 +4038,13 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) /* drop one or two expired sessions */ ngx_ssl_expire_sessions(cache, shpool, 1); - cached_sess = ngx_slab_alloc_locked(shpool, len); - - if (cached_sess == NULL) { - - /* drop the oldest non-expired session and try once more */ - - ngx_ssl_expire_sessions(cache, shpool, 0); - - cached_sess = ngx_slab_alloc_locked(shpool, len); - - if (cached_sess == NULL) { - sess_id = NULL; - goto failed; - } - } +#if (NGX_PTR_SIZE == 8) + n = sizeof(ngx_ssl_sess_id_t); +#else + n = offsetof(ngx_ssl_sess_id_t, session) + len; +#endif - sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t)); + sess_id = ngx_slab_alloc_locked(shpool, n); if (sess_id == NULL) { @@ -3845,41 +4052,34 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) ngx_ssl_expire_sessions(cache, shpool, 0); - sess_id = ngx_slab_alloc_locked(shpool, sizeof(ngx_ssl_sess_id_t)); + sess_id = ngx_slab_alloc_locked(shpool, n); if (sess_id == NULL) { goto failed; } } - session_id = (u_char *) SSL_SESSION_get_id(sess, &session_id_length); - #if (NGX_PTR_SIZE == 8) - id = sess_id->sess_id; - -#else - - id = ngx_slab_alloc_locked(shpool, session_id_length); + sess_id->session = ngx_slab_alloc_locked(shpool, len); - if (id == NULL) { + if (sess_id->session == NULL) { /* drop the oldest non-expired session and try once more */ ngx_ssl_expire_sessions(cache, shpool, 0); - id = ngx_slab_alloc_locked(shpool, session_id_length); + sess_id->session = ngx_slab_alloc_locked(shpool, len); - if (id == NULL) { + if (sess_id->session == NULL) { goto failed; } } #endif - ngx_memcpy(cached_sess, buf, len); - - ngx_memcpy(id, session_id, session_id_length); + ngx_memcpy(sess_id->session, buf, len); + ngx_memcpy(sess_id->id, session_id, session_id_length); hash = ngx_crc32_short(session_id, session_id_length); @@ -3889,9 +4089,7 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) sess_id->node.key = hash; sess_id->node.data = (u_char) session_id_length; - sess_id->id = id; sess_id->len = len; - sess_id->session = cached_sess; sess_id->expire = ngx_time() + SSL_CTX_get_timeout(ssl_ctx); @@ -3905,18 +4103,17 @@ ngx_ssl_new_session(ngx_ssl_conn_t *ssl_conn, ngx_ssl_session_t *sess) failed: - if (cached_sess) { - ngx_slab_free_locked(shpool, cached_sess); - } - if (sess_id) { ngx_slab_free_locked(shpool, sess_id); } ngx_shmtx_unlock(&shpool->mutex); - ngx_log_error(NGX_LOG_ALERT, c->log, 0, - "could not allocate new session%s", shpool->log_ctx); + if (cache->fail_time != ngx_time()) { + cache->fail_time = ngx_time(); + ngx_log_error(NGX_LOG_WARN, c->log, 0, + "could not allocate new session%s", shpool->log_ctx); + } return 0; } @@ -4002,9 +4199,10 @@ ngx_ssl_get_cached_session(ngx_ssl_conn_t *ssl_conn, ngx_rbtree_delete(&cache->session_rbtree, node); + ngx_explicit_memzero(sess_id->session, sess_id->len); + +#if (NGX_PTR_SIZE == 8) ngx_slab_free_locked(shpool, sess_id->session); -#if (NGX_PTR_SIZE == 4) - ngx_slab_free_locked(shpool, sess_id->id); #endif ngx_slab_free_locked(shpool, sess_id); @@ -4092,9 +4290,10 @@ ngx_ssl_remove_session(SSL_CTX *ssl, ngx_ssl_session_t *sess) ngx_rbtree_delete(&cache->session_rbtree, node); + ngx_explicit_memzero(sess_id->session, sess_id->len); + +#if (NGX_PTR_SIZE == 8) ngx_slab_free_locked(shpool, sess_id->session); -#if (NGX_PTR_SIZE == 4) - ngx_slab_free_locked(shpool, sess_id->id); #endif ngx_slab_free_locked(shpool, sess_id); @@ -4141,9 +4340,10 @@ ngx_ssl_expire_sessions(ngx_ssl_session_cache_t *cache, ngx_rbtree_delete(&cache->session_rbtree, &sess_id->node); + ngx_explicit_memzero(sess_id->session, sess_id->len); + +#if (NGX_PTR_SIZE == 8) ngx_slab_free_locked(shpool, sess_id->session); -#if (NGX_PTR_SIZE == 4) - ngx_slab_free_locked(shpool, sess_id->id); #endif ngx_slab_free_locked(shpool, sess_id); } @@ -4197,23 +4397,25 @@ ngx_ssl_session_rbtree_insert_value(ngx_rbtree_node_t *temp, ngx_int_t ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths) { - u_char buf[80]; - size_t size; - ssize_t n; - ngx_str_t *path; - ngx_file_t file; - ngx_uint_t i; - ngx_array_t *keys; - ngx_file_info_t fi; - ngx_pool_cleanup_t *cln; - ngx_ssl_session_ticket_key_t *key; - - if (paths == NULL) { + u_char buf[80]; + size_t size; + ssize_t n; + ngx_str_t *path; + ngx_file_t file; + ngx_uint_t i; + ngx_array_t *keys; + ngx_file_info_t fi; + ngx_pool_cleanup_t *cln; + ngx_ssl_ticket_key_t *key; + + if (paths == NULL + && SSL_CTX_get_ex_data(ssl->ctx, ngx_ssl_session_cache_index) == NULL) + { return NGX_OK; } - keys = ngx_array_create(cf->pool, paths->nelts, - sizeof(ngx_ssl_session_ticket_key_t)); + keys = ngx_array_create(cf->pool, paths ? paths->nelts : 3, + sizeof(ngx_ssl_ticket_key_t)); if (keys == NULL) { return NGX_ERROR; } @@ -4223,9 +4425,41 @@ ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths) return NGX_ERROR; } - cln->handler = ngx_ssl_session_ticket_keys_cleanup; + cln->handler = ngx_ssl_ticket_keys_cleanup; cln->data = keys; + if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_ticket_keys_index, keys) == 0) { + ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, + "SSL_CTX_set_ex_data() failed"); + return NGX_ERROR; + } + + if (SSL_CTX_set_tlsext_ticket_key_cb(ssl->ctx, ngx_ssl_ticket_key_callback) + == 0) + { + ngx_log_error(NGX_LOG_WARN, cf->log, 0, + "nginx was built with Session Tickets support, however, " + "now it is linked dynamically to an OpenSSL library " + "which has no tlsext support, therefore Session Tickets " + "are not available"); + return NGX_OK; + } + + if (paths == NULL) { + + /* placeholder for keys in shared memory */ + + key = ngx_array_push_n(keys, 3); + key[0].shared = 1; + key[0].expire = 0; + key[1].shared = 1; + key[1].expire = 0; + key[2].shared = 1; + key[2].expire = 0; + + return NGX_OK; + } + path = paths->elts; for (i = 0; i < paths->nelts; i++) { @@ -4280,6 +4514,9 @@ ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths) goto failed; } + key->shared = 0; + key->expire = 1; + if (size == 48) { key->size = 48; ngx_memcpy(key->name, buf, 16); @@ -4301,25 +4538,6 @@ ngx_ssl_session_ticket_keys(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_array_t *paths) ngx_explicit_memzero(&buf, 80); } - if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_session_ticket_keys_index, keys) - == 0) - { - ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, - "SSL_CTX_set_ex_data() failed"); - return NGX_ERROR; - } - - if (SSL_CTX_set_tlsext_ticket_key_cb(ssl->ctx, - ngx_ssl_session_ticket_key_callback) - == 0) - { - ngx_log_error(NGX_LOG_WARN, cf->log, 0, - "nginx was built with Session Tickets support, however, " - "now it is linked dynamically to an OpenSSL library " - "which has no tlsext support, therefore Session Tickets " - "are not available"); - } - return NGX_OK; failed: @@ -4336,29 +4554,33 @@ failed: static int -ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, +ngx_ssl_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, unsigned char *name, unsigned char *iv, EVP_CIPHER_CTX *ectx, HMAC_CTX *hctx, int enc) { - size_t size; - SSL_CTX *ssl_ctx; - ngx_uint_t i; - ngx_array_t *keys; - ngx_connection_t *c; - ngx_ssl_session_ticket_key_t *key; - const EVP_MD *digest; - const EVP_CIPHER *cipher; + size_t size; + SSL_CTX *ssl_ctx; + ngx_uint_t i; + ngx_array_t *keys; + ngx_connection_t *c; + ngx_ssl_ticket_key_t *key; + const EVP_MD *digest; + const EVP_CIPHER *cipher; c = ngx_ssl_get_connection(ssl_conn); ssl_ctx = c->ssl->session_ctx; + if (ngx_ssl_rotate_ticket_keys(ssl_ctx, c->log) != NGX_OK) { + return -1; + } + #ifdef OPENSSL_NO_SHA256 digest = EVP_sha1(); #else digest = EVP_sha256(); #endif - keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_ticket_keys_index); + keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ticket_keys_index); if (keys == NULL) { return -1; } @@ -4369,7 +4591,7 @@ ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, /* encrypt session ticket */ ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ssl session ticket encrypt, key: \"%*xs\" (%s session)", + "ssl ticket encrypt, key: \"%*xs\" (%s session)", (size_t) 16, key[0].name, SSL_session_reused(ssl_conn) ? "reused" : "new"); @@ -4416,7 +4638,7 @@ ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, } ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ssl session ticket decrypt, key: \"%*xs\" not found", + "ssl ticket decrypt, key: \"%*xs\" not found", (size_t) 16, name); return 0; @@ -4424,7 +4646,7 @@ ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, found: ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, - "ssl session ticket decrypt, key: \"%*xs\"%s", + "ssl ticket decrypt, key: \"%*xs\"%s", (size_t) 16, key[i].name, (i == 0) ? " (default)" : ""); if (key[i].size == 48) { @@ -4461,7 +4683,7 @@ ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, /* renew if non-default key */ - if (i != 0) { + if (i != 0 && key[i].expire) { return 2; } @@ -4470,13 +4692,142 @@ ngx_ssl_session_ticket_key_callback(ngx_ssl_conn_t *ssl_conn, } +static ngx_int_t +ngx_ssl_rotate_ticket_keys(SSL_CTX *ssl_ctx, ngx_log_t *log) +{ + time_t now, expire; + ngx_array_t *keys; + ngx_shm_zone_t *shm_zone; + ngx_slab_pool_t *shpool; + ngx_ssl_ticket_key_t *key; + ngx_ssl_session_cache_t *cache; + u_char buf[80]; + + keys = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_ticket_keys_index); + if (keys == NULL) { + return NGX_OK; + } + + key = keys->elts; + + if (!key[0].shared) { + return NGX_OK; + } + + /* + * if we don't need to update expiration of the current key + * and the previous key is still needed, don't sync with shared + * memory to save some work; in the worst case other worker process + * will switch to the next key, but this process will still be able + * to decrypt tickets encrypted with it + */ + + now = ngx_time(); + expire = now + SSL_CTX_get_timeout(ssl_ctx); + + if (key[0].expire >= expire && key[1].expire >= now) { + return NGX_OK; + } + + shm_zone = SSL_CTX_get_ex_data(ssl_ctx, ngx_ssl_session_cache_index); + + cache = shm_zone->data; + shpool = (ngx_slab_pool_t *) shm_zone->shm.addr; + + ngx_shmtx_lock(&shpool->mutex); + + key = cache->ticket_keys; + + if (key[0].expire == 0) { + + /* initialize the current key */ + + if (RAND_bytes(buf, 80) != 1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed"); + ngx_shmtx_unlock(&shpool->mutex); + return NGX_ERROR; + } + + key[0].shared = 1; + key[0].expire = expire; + key[0].size = 80; + ngx_memcpy(key[0].name, buf, 16); + ngx_memcpy(key[0].hmac_key, buf + 16, 32); + ngx_memcpy(key[0].aes_key, buf + 48, 32); + + ngx_explicit_memzero(&buf, 80); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "ssl ticket key: \"%*xs\"", + (size_t) 16, key[0].name); + + /* + * copy the current key to the next key, as initialization of + * the previous key will replace the current key with the next + * key + */ + + key[2] = key[0]; + } + + if (key[1].expire < now) { + + /* + * if the previous key is no longer needed (or not initialized), + * replace it with the current key, replace the current key with + * the next key, and generate new next key + */ + + key[1] = key[0]; + key[0] = key[2]; + + if (RAND_bytes(buf, 80) != 1) { + ngx_ssl_error(NGX_LOG_ALERT, log, 0, "RAND_bytes() failed"); + ngx_shmtx_unlock(&shpool->mutex); + return NGX_ERROR; + } + + key[2].shared = 1; + key[2].expire = 0; + key[2].size = 80; + ngx_memcpy(key[2].name, buf, 16); + ngx_memcpy(key[2].hmac_key, buf + 16, 32); + ngx_memcpy(key[2].aes_key, buf + 48, 32); + + ngx_explicit_memzero(&buf, 80); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "ssl ticket key: \"%*xs\"", + (size_t) 16, key[2].name); + } + + /* + * update expiration of the current key: it is going to be needed + * at least till the session being created expires + */ + + if (expire > key[0].expire) { + key[0].expire = expire; + } + + /* sync keys to the worker process memory */ + + ngx_memcpy(keys->elts, cache->ticket_keys, + 2 * sizeof(ngx_ssl_ticket_key_t)); + + ngx_shmtx_unlock(&shpool->mutex); + + return NGX_OK; +} + + static void -ngx_ssl_session_ticket_keys_cleanup(void *data) +ngx_ssl_ticket_keys_cleanup(void *data) { ngx_array_t *keys = data; ngx_explicit_memzero(keys->elts, - keys->nelts * sizeof(ngx_ssl_session_ticket_key_t)); + keys->nelts * sizeof(ngx_ssl_ticket_key_t)); } #else @@ -4836,6 +5187,9 @@ ngx_ssl_get_curves(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) } curves = ngx_palloc(pool, n * sizeof(int)); + if (curves == NULL) { + return NGX_ERROR; + } n = SSL_get1_curves(c->ssl->connection, curves); len = 0; diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index c9e86d9..ebb2c35 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -24,6 +24,14 @@ #include <openssl/engine.h> #endif #include <openssl/evp.h> +#if (NGX_QUIC) +#ifdef OPENSSL_IS_BORINGSSL +#include <openssl/hkdf.h> +#include <openssl/chacha.h> +#else +#include <openssl/kdf.h> +#endif +#endif #include <openssl/hmac.h> #ifndef OPENSSL_NO_OCSP #include <openssl/ocsp.h> @@ -37,7 +45,7 @@ #if (defined LIBRESSL_VERSION_NUMBER && OPENSSL_VERSION_NUMBER == 0x20000000L) #undef OPENSSL_VERSION_NUMBER -#if (LIBRESSL_VERSION_NUMBER >= 0x2080000fL) +#if (LIBRESSL_VERSION_NUMBER >= 0x3050000fL) #define OPENSSL_VERSION_NUMBER 0x1010000fL #else #define OPENSSL_VERSION_NUMBER 0x1000107fL @@ -114,6 +122,7 @@ struct ngx_ssl_connection_s { unsigned no_send_shutdown:1; unsigned shutdown_without_free:1; unsigned handshake_buffer_set:1; + unsigned session_timeout_set:1; unsigned try_early_data:1; unsigned in_early:1; unsigned in_ocsp:1; @@ -134,35 +143,35 @@ typedef struct ngx_ssl_sess_id_s ngx_ssl_sess_id_t; struct ngx_ssl_sess_id_s { ngx_rbtree_node_t node; - u_char *id; size_t len; - u_char *session; ngx_queue_t queue; time_t expire; + u_char id[32]; #if (NGX_PTR_SIZE == 8) - void *stub; - u_char sess_id[32]; + u_char *session; +#else + u_char session[1]; #endif }; typedef struct { - ngx_rbtree_t session_rbtree; - ngx_rbtree_node_t sentinel; - ngx_queue_t expire_queue; -} ngx_ssl_session_cache_t; - - -#ifdef SSL_CTRL_SET_TLSEXT_TICKET_KEY_CB - -typedef struct { - size_t size; u_char name[16]; u_char hmac_key[32]; u_char aes_key[32]; -} ngx_ssl_session_ticket_key_t; + time_t expire; + unsigned size:8; + unsigned shared:1; +} ngx_ssl_ticket_key_t; -#endif + +typedef struct { + ngx_rbtree_t session_rbtree; + ngx_rbtree_node_t sentinel; + ngx_queue_t expire_queue; + ngx_ssl_ticket_key_t ticket_keys[3]; + time_t fail_time; +} ngx_ssl_session_cache_t; #define NGX_SSL_SSLv2 0x0002 @@ -204,10 +213,12 @@ ngx_int_t ngx_ssl_ocsp(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *responder, ngx_uint_t depth, ngx_shm_zone_t *shm_zone); ngx_int_t ngx_ssl_ocsp_resolver(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_resolver_t *resolver, ngx_msec_t resolver_timeout); + ngx_int_t ngx_ssl_ocsp_validate(ngx_connection_t *c); ngx_int_t ngx_ssl_ocsp_get_status(ngx_connection_t *c, const char **s); void ngx_ssl_ocsp_cleanup(ngx_connection_t *c); ngx_int_t ngx_ssl_ocsp_cache_init(ngx_shm_zone_t *shm_zone, void *data); + ngx_array_t *ngx_ssl_read_password_file(ngx_conf_t *cf, ngx_str_t *file); ngx_array_t *ngx_ssl_preserve_passwords(ngx_conf_t *cf, ngx_array_t *passwords); @@ -299,6 +310,9 @@ ngx_int_t ngx_ssl_get_client_v_remain(ngx_connection_t *c, ngx_pool_t *pool, ngx_int_t ngx_ssl_handshake(ngx_connection_t *c); +#if (NGX_DEBUG) +void ngx_ssl_handshake_log(ngx_connection_t *c); +#endif ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size); ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size); ssize_t ngx_ssl_recv_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t limit); @@ -314,7 +328,7 @@ void ngx_ssl_cleanup_ctx(void *data); extern int ngx_ssl_connection_index; extern int ngx_ssl_server_conf_index; extern int ngx_ssl_session_cache_index; -extern int ngx_ssl_session_ticket_keys_index; +extern int ngx_ssl_ticket_keys_index; extern int ngx_ssl_ocsp_index; extern int ngx_ssl_certificate_index; extern int ngx_ssl_next_certificate_index; diff --git a/src/event/ngx_event_openssl_stapling.c b/src/event/ngx_event_openssl_stapling.c index e3fa8c4..e9bb835 100644 --- a/src/event/ngx_event_openssl_stapling.c +++ b/src/event/ngx_event_openssl_stapling.c @@ -893,7 +893,7 @@ ngx_ssl_ocsp_validate(ngx_connection_t *c) ocsp->cert_status = V_OCSP_CERTSTATUS_GOOD; ocsp->conf = ocf; -#if (OPENSSL_VERSION_NUMBER >= 0x10100000L && !defined LIBRESSL_VERSION_NUMBER) +#if OPENSSL_VERSION_NUMBER >= 0x10100000L ocsp->certs = SSL_get0_verified_chain(c->ssl->connection); diff --git a/src/event/ngx_event_pipe.c b/src/event/ngx_event_pipe.c index 54412e1..d774903 100644 --- a/src/event/ngx_event_pipe.c +++ b/src/event/ngx_event_pipe.c @@ -57,7 +57,9 @@ ngx_event_pipe(ngx_event_pipe_t *p, ngx_int_t do_write) do_write = 1; } - if (p->upstream->fd != (ngx_socket_t) -1) { + if (p->upstream + && p->upstream->fd != (ngx_socket_t) -1) + { rev = p->upstream->read; flags = (rev->eof || rev->error) ? NGX_CLOSE_EVENT : 0; @@ -108,7 +110,9 @@ ngx_event_pipe_read_upstream(ngx_event_pipe_t *p) ngx_msec_t delay; ngx_chain_t *chain, *cl, *ln; - if (p->upstream_eof || p->upstream_error || p->upstream_done) { + if (p->upstream_eof || p->upstream_error || p->upstream_done + || p->upstream == NULL) + { return NGX_OK; } diff --git a/src/event/ngx_event_udp.c b/src/event/ngx_event_udp.c index a524ae0..43fa621 100644 --- a/src/event/ngx_event_udp.c +++ b/src/event/ngx_event_udp.c @@ -12,13 +12,6 @@ #if !(NGX_WIN32) -struct ngx_udp_connection_s { - ngx_rbtree_node_t node; - ngx_connection_t *connection; - ngx_buf_t *buffer; -}; - - static void ngx_close_accepted_udp_connection(ngx_connection_t *c); static ssize_t ngx_udp_shared_recv(ngx_connection_t *c, u_char *buf, size_t size); @@ -46,18 +39,8 @@ ngx_event_recvmsg(ngx_event_t *ev) ngx_connection_t *c, *lc; static u_char buffer[65535]; -#if (NGX_HAVE_MSGHDR_MSG_CONTROL) - -#if (NGX_HAVE_IP_RECVDSTADDR) - u_char msg_control[CMSG_SPACE(sizeof(struct in_addr))]; -#elif (NGX_HAVE_IP_PKTINFO) - u_char msg_control[CMSG_SPACE(sizeof(struct in_pktinfo))]; -#endif - -#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) - u_char msg_control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; -#endif - +#if (NGX_HAVE_ADDRINFO_CMSG) + u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; #endif if (ev->timedout) { @@ -92,25 +75,13 @@ ngx_event_recvmsg(ngx_event_t *ev) msg.msg_iov = iov; msg.msg_iovlen = 1; -#if (NGX_HAVE_MSGHDR_MSG_CONTROL) - +#if (NGX_HAVE_ADDRINFO_CMSG) if (ls->wildcard) { + msg.msg_control = &msg_control; + msg.msg_controllen = sizeof(msg_control); -#if (NGX_HAVE_IP_RECVDSTADDR || NGX_HAVE_IP_PKTINFO) - if (ls->sockaddr->sa_family == AF_INET) { - msg.msg_control = &msg_control; - msg.msg_controllen = sizeof(msg_control); - } -#endif - -#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) - if (ls->sockaddr->sa_family == AF_INET6) { - msg.msg_control = &msg_control6; - msg.msg_controllen = sizeof(msg_control6); - } -#endif + ngx_memzero(&msg_control, sizeof(msg_control)); } - #endif n = recvmsg(lc->fd, &msg, 0); @@ -129,7 +100,7 @@ ngx_event_recvmsg(ngx_event_t *ev) return; } -#if (NGX_HAVE_MSGHDR_MSG_CONTROL) +#if (NGX_HAVE_ADDRINFO_CMSG) if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { ngx_log_error(NGX_LOG_ALERT, ev->log, 0, "recvmsg() truncated data"); @@ -159,7 +130,7 @@ ngx_event_recvmsg(ngx_event_t *ev) local_sockaddr = ls->sockaddr; local_socklen = ls->socklen; -#if (NGX_HAVE_MSGHDR_MSG_CONTROL) +#if (NGX_HAVE_ADDRINFO_CMSG) if (ls->wildcard) { struct cmsghdr *cmsg; @@ -171,59 +142,9 @@ ngx_event_recvmsg(ngx_event_t *ev) cmsg != NULL; cmsg = CMSG_NXTHDR(&msg, cmsg)) { - -#if (NGX_HAVE_IP_RECVDSTADDR) - - if (cmsg->cmsg_level == IPPROTO_IP - && cmsg->cmsg_type == IP_RECVDSTADDR - && local_sockaddr->sa_family == AF_INET) - { - struct in_addr *addr; - struct sockaddr_in *sin; - - addr = (struct in_addr *) CMSG_DATA(cmsg); - sin = (struct sockaddr_in *) local_sockaddr; - sin->sin_addr = *addr; - + if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) { break; } - -#elif (NGX_HAVE_IP_PKTINFO) - - if (cmsg->cmsg_level == IPPROTO_IP - && cmsg->cmsg_type == IP_PKTINFO - && local_sockaddr->sa_family == AF_INET) - { - struct in_pktinfo *pkt; - struct sockaddr_in *sin; - - pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); - sin = (struct sockaddr_in *) local_sockaddr; - sin->sin_addr = pkt->ipi_addr; - - break; - } - -#endif - -#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) - - if (cmsg->cmsg_level == IPPROTO_IPV6 - && cmsg->cmsg_type == IPV6_PKTINFO - && local_sockaddr->sa_family == AF_INET6) - { - struct in6_pktinfo *pkt6; - struct sockaddr_in6 *sin6; - - pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); - sin6 = (struct sockaddr_in6 *) local_sockaddr; - sin6->sin6_addr = pkt6->ipi6_addr; - - break; - } - -#endif - } } @@ -318,6 +239,8 @@ ngx_event_recvmsg(ngx_event_t *ev) c->send = ngx_udp_send; c->send_chain = ngx_udp_send_chain; + c->need_flush_buf = 1; + c->log = log; c->pool->log = log; c->listening = ls; @@ -494,8 +417,8 @@ ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, udpt = (ngx_udp_connection_t *) temp; ct = udpt->connection; - rc = ngx_cmp_sockaddr(c->sockaddr, c->socklen, - ct->sockaddr, ct->socklen, 1); + rc = ngx_memn2cmp(udp->key.data, udpt->key.data, + udp->key.len, udpt->key.len); if (rc == 0 && c->listening->wildcard) { rc = ngx_cmp_sockaddr(c->local_sockaddr, c->local_socklen, @@ -548,6 +471,8 @@ ngx_insert_udp_connection(ngx_connection_t *c) ngx_crc32_final(hash); udp->node.key = hash; + udp->key.data = (u_char *) c->sockaddr; + udp->key.len = c->socklen; cln = ngx_pool_cleanup_add(c->pool, 0); if (cln == NULL) { diff --git a/src/event/ngx_event_udp.h b/src/event/ngx_event_udp.h new file mode 100644 index 0000000..e5ddf1b --- /dev/null +++ b/src/event/ngx_event_udp.h @@ -0,0 +1,66 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_UDP_H_INCLUDED_ +#define _NGX_EVENT_UDP_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +#if !(NGX_WIN32) + +#if ((NGX_HAVE_MSGHDR_MSG_CONTROL) \ + && (NGX_HAVE_IP_SENDSRCADDR || NGX_HAVE_IP_RECVDSTADDR \ + || NGX_HAVE_IP_PKTINFO \ + || (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO))) +#define NGX_HAVE_ADDRINFO_CMSG 1 + +#endif + + +struct ngx_udp_connection_s { + ngx_rbtree_node_t node; + ngx_connection_t *connection; + ngx_buf_t *buffer; + ngx_str_t key; +}; + + +#if (NGX_HAVE_ADDRINFO_CMSG) + +typedef union { +#if (NGX_HAVE_IP_SENDSRCADDR || NGX_HAVE_IP_RECVDSTADDR) + struct in_addr addr; +#endif + +#if (NGX_HAVE_IP_PKTINFO) + struct in_pktinfo pkt; +#endif + +#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) + struct in6_pktinfo pkt6; +#endif +} ngx_addrinfo_t; + +size_t ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg, + struct sockaddr *local_sockaddr); +ngx_int_t ngx_get_srcaddr_cmsg(struct cmsghdr *cmsg, + struct sockaddr *local_sockaddr); + +#endif + +void ngx_event_recvmsg(ngx_event_t *ev); +ssize_t ngx_sendmsg(ngx_connection_t *c, struct msghdr *msg, int flags); +void ngx_udp_rbtree_insert_value(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +#endif + +void ngx_delete_udp_connection(void *data); + + +#endif /* _NGX_EVENT_UDP_H_INCLUDED_ */ diff --git a/src/event/quic/bpf/bpfgen.sh b/src/event/quic/bpf/bpfgen.sh new file mode 100644 index 0000000..78cbdac --- /dev/null +++ b/src/event/quic/bpf/bpfgen.sh @@ -0,0 +1,113 @@ +#!/bin/bash + +export LANG=C + +set -e + +if [ $# -lt 1 ]; then + echo "Usage: PROGNAME=foo LICENSE=bar $0 <bpf object file>" + exit 1 +fi + + +self=$0 +filename=$1 +funcname=$PROGNAME + +generate_head() +{ + cat << END +/* AUTO-GENERATED, DO NOT EDIT. */ + +#include <stddef.h> +#include <stdint.h> + +#include "ngx_bpf.h" + + +END +} + +generate_tail() +{ + cat << END + +ngx_bpf_program_t $PROGNAME = { + .relocs = bpf_reloc_prog_$funcname, + .nrelocs = sizeof(bpf_reloc_prog_$funcname) + / sizeof(bpf_reloc_prog_$funcname[0]), + .ins = bpf_insn_prog_$funcname, + .nins = sizeof(bpf_insn_prog_$funcname) + / sizeof(bpf_insn_prog_$funcname[0]), + .license = "$LICENSE", + .type = BPF_PROG_TYPE_SK_REUSEPORT, +}; + +END +} + +process_relocations() +{ + echo "static ngx_bpf_reloc_t bpf_reloc_prog_$funcname[] = {" + + objdump -r $filename | awk '{ + + if (enabled && $NF > 0) { + off = strtonum(sprintf("0x%s", $1)); + name = $3; + + printf(" { \"%s\", %d },\n", name, off/8); + } + + if ($1 == "OFFSET") { + enabled=1; + } +}' + echo "};" + echo +} + +process_section() +{ + echo "static struct bpf_insn bpf_insn_prog_$funcname[] = {" + echo " /* opcode dst src offset imm */" + + section_info=$(objdump -h $filename --section=$funcname | grep "1 $funcname") + + # dd doesn't know hex + length=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f3)) + offset=$(printf "%d" 0x$(echo $section_info | cut -d ' ' -f6)) + + for ins in $(dd if="$filename" bs=1 count=$length skip=$offset status=none | xxd -p -c 8) + do + opcode=0x${ins:0:2} + srcdst=0x${ins:2:2} + + # bytes are dumped in LE order + offset=0x${ins:6:2}${ins:4:2} # short + immedi=0x${ins:14:2}${ins:12:2}${ins:10:2}${ins:8:2} # int + + dst="$(($srcdst & 0xF))" + src="$(($srcdst & 0xF0))" + src="$(($src >> 4))" + + opcode=$(printf "0x%x" $opcode) + dst=$(printf "BPF_REG_%d" $dst) + src=$(printf "BPF_REG_%d" $src) + offset=$(printf "%d" $offset) + immedi=$(printf "0x%x" $immedi) + + printf " { %4s, %11s, %11s, (int16_t) %6s, %10s },\n" $opcode $dst $src $offset $immedi + done + +cat << END +}; + +END +} + +generate_head +process_relocations +process_section +generate_tail + diff --git a/src/event/quic/bpf/makefile b/src/event/quic/bpf/makefile new file mode 100644 index 0000000..b4d758f --- /dev/null +++ b/src/event/quic/bpf/makefile @@ -0,0 +1,30 @@ +CFLAGS=-O2 -Wall + +LICENSE=BSD + +PROGNAME=ngx_quic_reuseport_helper +RESULT=ngx_event_quic_bpf_code +DEST=../$(RESULT).c + +all: $(RESULT) + +$(RESULT): $(PROGNAME).o + LICENSE=$(LICENSE) PROGNAME=$(PROGNAME) bash ./bpfgen.sh $< > $@ + +DEFS=-DPROGNAME=\"$(PROGNAME)\" \ + -DLICENSE_$(LICENSE) \ + -DLICENSE=\"$(LICENSE)\" \ + +$(PROGNAME).o: $(PROGNAME).c + clang $(CFLAGS) $(DEFS) -target bpf -c $< -o $@ + +install: $(RESULT) + cp $(RESULT) $(DEST) + +clean: + @rm -f $(RESULT) *.o + +debug: $(PROGNAME).o + llvm-objdump -S -no-show-raw-insn $< + +.DELETE_ON_ERROR: diff --git a/src/event/quic/bpf/ngx_quic_reuseport_helper.c b/src/event/quic/bpf/ngx_quic_reuseport_helper.c new file mode 100644 index 0000000..999e760 --- /dev/null +++ b/src/event/quic/bpf/ngx_quic_reuseport_helper.c @@ -0,0 +1,140 @@ +#include <errno.h> +#include <linux/string.h> +#include <linux/udp.h> +#include <linux/bpf.h> +/* + * the bpf_helpers.h is not included into linux-headers, only available + * with kernel sources in "tools/lib/bpf/bpf_helpers.h" or in libbpf. + */ +#include <bpf/bpf_helpers.h> + + +#if !defined(SEC) +#define SEC(NAME) __attribute__((section(NAME), used)) +#endif + + +#if defined(LICENSE_GPL) + +/* + * To see debug: + * + * echo 1 > /sys/kernel/debug/tracing/events/bpf_trace/enable + * cat /sys/kernel/debug/tracing/trace_pipe + * echo 0 > /sys/kernel/debug/tracing/events/bpf_trace/enable + */ + +#define debugmsg(fmt, ...) \ +do { \ + char __buf[] = fmt; \ + bpf_trace_printk(__buf, sizeof(__buf), ##__VA_ARGS__); \ +} while (0) + +#else + +#define debugmsg(fmt, ...) + +#endif + +char _license[] SEC("license") = LICENSE; + +/*****************************************************************************/ + +#define NGX_QUIC_PKT_LONG 0x80 /* header form */ +#define NGX_QUIC_SERVER_CID_LEN 20 + + +#define advance_data(nbytes) \ + offset += nbytes; \ + if (start + offset > end) { \ + debugmsg("cannot read %ld bytes at offset %ld", nbytes, offset); \ + goto failed; \ + } \ + data = start + offset - 1; + + +#define ngx_quic_parse_uint64(p) \ + (((__u64)(p)[0] << 56) | \ + ((__u64)(p)[1] << 48) | \ + ((__u64)(p)[2] << 40) | \ + ((__u64)(p)[3] << 32) | \ + ((__u64)(p)[4] << 24) | \ + ((__u64)(p)[5] << 16) | \ + ((__u64)(p)[6] << 8) | \ + ((__u64)(p)[7])) + +/* + * actual map object is created by the "bpf" system call, + * all pointers to this variable are replaced by the bpf loader + */ +struct bpf_map_def SEC("maps") ngx_quic_sockmap; + + +SEC(PROGNAME) +int ngx_quic_select_socket_by_dcid(struct sk_reuseport_md *ctx) +{ + int rc; + __u64 key; + size_t len, offset; + unsigned char *start, *end, *data, *dcid; + + start = ctx->data; + end = (unsigned char *) ctx->data_end; + offset = 0; + + advance_data(sizeof(struct udphdr)); /* data at UDP header */ + advance_data(1); /* data at QUIC flags */ + + if (data[0] & NGX_QUIC_PKT_LONG) { + + advance_data(4); /* data at QUIC version */ + advance_data(1); /* data at DCID len */ + + len = data[0]; /* read DCID length */ + + if (len < 8) { + /* it's useless to search for key in such short DCID */ + return SK_PASS; + } + + } else { + len = NGX_QUIC_SERVER_CID_LEN; + } + + dcid = &data[1]; + advance_data(len); /* we expect the packet to have full DCID */ + + /* make verifier happy */ + if (dcid + sizeof(__u64) > end) { + goto failed; + } + + key = ngx_quic_parse_uint64(dcid); + + rc = bpf_sk_select_reuseport(ctx, &ngx_quic_sockmap, &key, 0); + + switch (rc) { + case 0: + debugmsg("nginx quic socket selected by key 0x%llx", key); + return SK_PASS; + + /* kernel returns positive error numbers, errno.h defines positive */ + case -ENOENT: + debugmsg("nginx quic default route for key 0x%llx", key); + /* let the default reuseport logic decide which socket to choose */ + return SK_PASS; + + default: + debugmsg("nginx quic bpf_sk_select_reuseport err: %d key 0x%llx", + rc, key); + goto failed; + } + +failed: + /* + * SK_DROP will generate ICMP, but we may want to process "invalid" packet + * in userspace quic to investigate further and finally react properly + * (maybe ignore, maybe send something in response or close connection) + */ + return SK_PASS; +} diff --git a/src/event/quic/ngx_event_quic.c b/src/event/quic/ngx_event_quic.c new file mode 100644 index 0000000..e4690f7 --- /dev/null +++ b/src/event/quic/ngx_event_quic.c @@ -0,0 +1,1452 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + + +static ngx_quic_connection_t *ngx_quic_new_connection(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_handle_stateless_reset(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static void ngx_quic_input_handler(ngx_event_t *rev); +static void ngx_quic_close_handler(ngx_event_t *ev); + +static ngx_int_t ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, + ngx_quic_conf_t *conf); +static ngx_int_t ngx_quic_handle_packet(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_handle_payload(ngx_connection_t *c, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_check_csid(ngx_quic_connection_t *qc, + ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_handle_frames(ngx_connection_t *c, + ngx_quic_header_t *pkt); + +static void ngx_quic_push_handler(ngx_event_t *ev); + + +static ngx_core_module_t ngx_quic_module_ctx = { + ngx_string("quic"), + NULL, + NULL +}; + + +ngx_module_t ngx_quic_module = { + NGX_MODULE_V1, + &ngx_quic_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +#if (NGX_DEBUG) + +void +ngx_quic_connstate_dbg(ngx_connection_t *c) +{ + u_char *p, *last; + ngx_quic_connection_t *qc; + u_char buf[NGX_MAX_ERROR_STR]; + + p = buf; + last = p + sizeof(buf); + + qc = ngx_quic_get_connection(c); + + p = ngx_slprintf(p, last, "state:"); + + if (qc) { + + if (qc->error != (ngx_uint_t) -1) { + p = ngx_slprintf(p, last, "%s", qc->error_app ? " app" : ""); + p = ngx_slprintf(p, last, " error:%ui", qc->error); + + if (qc->error_reason) { + p = ngx_slprintf(p, last, " \"%s\"", qc->error_reason); + } + } + + p = ngx_slprintf(p, last, "%s", qc->shutdown ? " shutdown" : ""); + p = ngx_slprintf(p, last, "%s", qc->closing ? " closing" : ""); + p = ngx_slprintf(p, last, "%s", qc->draining ? " draining" : ""); + p = ngx_slprintf(p, last, "%s", qc->key_phase ? " kp" : ""); + + } else { + p = ngx_slprintf(p, last, " early"); + } + + if (c->read->timer_set) { + p = ngx_slprintf(p, last, + qc && qc->send_timer_set ? " send:%M" : " read:%M", + c->read->timer.key - ngx_current_msec); + } + + if (qc) { + + if (qc->push.timer_set) { + p = ngx_slprintf(p, last, " push:%M", + qc->push.timer.key - ngx_current_msec); + } + + if (qc->pto.timer_set) { + p = ngx_slprintf(p, last, " pto:%M", + qc->pto.timer.key - ngx_current_msec); + } + + if (qc->close.timer_set) { + p = ngx_slprintf(p, last, " close:%M", + qc->close.timer.key - ngx_current_msec); + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic %*s", p - buf, buf); +} + +#endif + + +ngx_int_t +ngx_quic_apply_transport_params(ngx_connection_t *c, ngx_quic_tp_t *ctp) +{ + ngx_str_t scid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + scid.data = qc->path->cid->id; + scid.len = qc->path->cid->len; + + if (scid.len != ctp->initial_scid.len + || ngx_memcmp(scid.data, ctp->initial_scid.data, scid.len) != 0) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic client initial_source_connection_id mismatch"); + return NGX_ERROR; + } + + if (ctp->max_udp_payload_size < NGX_QUIC_MIN_INITIAL_SIZE + || ctp->max_udp_payload_size > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid maximum packet size"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic maximum packet size is invalid"); + return NGX_ERROR; + } + + if (ctp->active_connection_id_limit < 2) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid active_connection_id_limit"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic active_connection_id_limit is invalid"); + return NGX_ERROR; + } + + if (ctp->ack_delay_exponent > 20) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid ack_delay_exponent"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic ack_delay_exponent is invalid"); + return NGX_ERROR; + } + + if (ctp->max_ack_delay >= 16384) { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "invalid max_ack_delay"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic max_ack_delay is invalid"); + return NGX_ERROR; + } + + if (ctp->max_idle_timeout > 0 + && ctp->max_idle_timeout < qc->tp.max_idle_timeout) + { + qc->tp.max_idle_timeout = ctp->max_idle_timeout; + } + + qc->streams.server_max_streams_bidi = ctp->initial_max_streams_bidi; + qc->streams.server_max_streams_uni = ctp->initial_max_streams_uni; + + ngx_memcpy(&qc->ctp, ctp, sizeof(ngx_quic_tp_t)); + + return NGX_OK; +} + + +void +ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf) +{ + ngx_int_t rc; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic run"); + + rc = ngx_quic_handle_datagram(c, c->buffer, conf); + if (rc != NGX_OK) { + ngx_quic_close_connection(c, rc); + return; + } + + /* quic connection is now created */ + qc = ngx_quic_get_connection(c); + + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + + if (!qc->streams.initialized) { + ngx_add_timer(&qc->close, qc->conf->handshake_timeout); + } + + ngx_quic_connstate_dbg(c); + + c->read->handler = ngx_quic_input_handler; + + return; +} + + +static ngx_quic_connection_t * +ngx_quic_new_connection(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + ngx_uint_t i; + ngx_quic_tp_t *ctp; + ngx_quic_connection_t *qc; + + qc = ngx_pcalloc(c->pool, sizeof(ngx_quic_connection_t)); + if (qc == NULL) { + return NULL; + } + + qc->keys = ngx_pcalloc(c->pool, sizeof(ngx_quic_keys_t)); + if (qc->keys == NULL) { + return NULL; + } + + qc->version = pkt->version; + + ngx_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, + ngx_quic_rbtree_insert_stream); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_queue_init(&qc->send_ctx[i].frames); + ngx_queue_init(&qc->send_ctx[i].sending); + ngx_queue_init(&qc->send_ctx[i].sent); + qc->send_ctx[i].largest_pn = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].largest_ack = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].largest_range = NGX_QUIC_UNSET_PN; + qc->send_ctx[i].pending_ack = NGX_QUIC_UNSET_PN; + } + + qc->send_ctx[0].level = ssl_encryption_initial; + qc->send_ctx[1].level = ssl_encryption_handshake; + qc->send_ctx[2].level = ssl_encryption_application; + + ngx_queue_init(&qc->free_frames); + + ngx_quic_init_rtt(qc); + + qc->pto.log = c->log; + qc->pto.data = c; + qc->pto.handler = ngx_quic_pto_handler; + + qc->push.log = c->log; + qc->push.data = c; + qc->push.handler = ngx_quic_push_handler; + + qc->close.log = c->log; + qc->close.data = c; + qc->close.handler = ngx_quic_close_handler; + + qc->path_validation.log = c->log; + qc->path_validation.data = c; + qc->path_validation.handler = ngx_quic_path_handler; + + qc->key_update.log = c->log; + qc->key_update.data = c; + qc->key_update.handler = ngx_quic_keys_update; + + qc->conf = conf; + + if (ngx_quic_init_transport_params(&qc->tp, conf) != NGX_OK) { + return NULL; + } + + ctp = &qc->ctp; + + /* defaults to be used before actual client parameters are received */ + ctp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; + ctp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; + ctp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; + ctp->active_connection_id_limit = 2; + + ngx_queue_init(&qc->streams.uninitialized); + ngx_queue_init(&qc->streams.free); + + qc->streams.recv_max_data = qc->tp.initial_max_data; + qc->streams.recv_window = qc->streams.recv_max_data; + + qc->streams.client_max_streams_uni = qc->tp.initial_max_streams_uni; + qc->streams.client_max_streams_bidi = qc->tp.initial_max_streams_bidi; + + qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, + ngx_max(2 * qc->tp.max_udp_payload_size, + 14720)); + qc->congestion.ssthresh = (size_t) -1; + qc->congestion.recovery_start = ngx_current_msec; + + if (pkt->validated && pkt->retried) { + qc->tp.retry_scid.len = pkt->dcid.len; + qc->tp.retry_scid.data = ngx_pstrdup(c->pool, &pkt->dcid); + if (qc->tp.retry_scid.data == NULL) { + return NULL; + } + } + + if (ngx_quic_keys_set_initial_secret(qc->keys, &pkt->dcid, c->log) + != NGX_OK) + { + return NULL; + } + + qc->validated = pkt->validated; + + if (ngx_quic_open_sockets(c, qc, pkt) != NGX_OK) { + ngx_quic_keys_cleanup(qc->keys); + return NULL; + } + + c->idle = 1; + ngx_reusable_connection(c, 1); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection created"); + + return qc; +} + + +static ngx_int_t +ngx_quic_handle_stateless_reset(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *tail, ch; + ngx_uint_t i; + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* A stateless reset uses an entire UDP datagram */ + if (!pkt->first) { + return NGX_DECLINED; + } + + tail = pkt->raw->last - NGX_QUIC_SR_TOKEN_LEN; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (cid->seqnum == 0 || !cid->used) { + /* + * No stateless reset token in initial connection id. + * Don't accept a token from an unused connection id. + */ + continue; + } + + /* constant time comparison */ + + for (ch = 0, i = 0; i < NGX_QUIC_SR_TOKEN_LEN; i++) { + ch |= tail[i] ^ cid->sr_token[i]; + } + + if (ch == 0) { + return NGX_OK; + } + } + + return NGX_DECLINED; +} + + +static void +ngx_quic_input_handler(ngx_event_t *rev) +{ + ngx_int_t rc; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, rev->log, 0, "quic input handler"); + + c = rev->data; + qc = ngx_quic_get_connection(c); + + c->log->action = "handling quic input"; + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, + "quic client timed out"); + ngx_quic_close_connection(c, NGX_DONE); + return; + } + + if (c->close) { + c->close = 0; + + if (!ngx_exiting || !qc->streams.initialized) { + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "graceful shutdown"; + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + if (!qc->closing && qc->conf->shutdown) { + qc->conf->shutdown(c); + } + + return; + } + + b = c->udp->buffer; + if (b == NULL) { + return; + } + + rc = ngx_quic_handle_datagram(c, b, NULL); + + if (rc == NGX_ERROR) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + if (rc == NGX_DONE) { + return; + } + + /* rc == NGX_OK */ + + qc->send_timer_set = 0; + ngx_add_timer(rev, qc->tp.max_idle_timeout); + + ngx_quic_connstate_dbg(c); +} + + +void +ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc) +{ + ngx_uint_t i; + ngx_pool_t *pool; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rejected rc:%i, cleanup connection", rc); + goto quic_done; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close %s rc:%i", + qc->closing ? "resumed": "initiated", rc); + + if (!qc->closing) { + + /* drop packets from retransmit queues, no ack is expected */ + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_free_frames(c, &qc->send_ctx[i].frames); + ngx_quic_free_frames(c, &qc->send_ctx[i].sent); + } + + if (qc->close.timer_set) { + ngx_del_timer(&qc->close); + } + + if (rc == NGX_DONE) { + + /* + * RFC 9000, 10.1. Idle Timeout + * + * If a max_idle_timeout is specified by either endpoint in its + * transport parameters (Section 18.2), the connection is silently + * closed and its state is discarded when it remains idle + */ + + /* this case also handles some errors from ngx_quic_run() */ + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close silent drain:%d timedout:%d", + qc->draining, c->read->timedout); + } else { + + /* + * RFC 9000, 10.2. Immediate Close + * + * An endpoint sends a CONNECTION_CLOSE frame (Section 19.19) + * to terminate the connection immediately. + */ + + if (qc->error == (ngx_uint_t) -1) { + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; + qc->error_app = 0; + } + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic close immediate term:%d drain:%d " + "%serror:%ui \"%s\"", + rc == NGX_ERROR ? 1 : 0, qc->draining, + qc->error_app ? "app " : "", qc->error, + qc->error_reason ? qc->error_reason : ""); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i]; + + if (!ngx_quic_keys_available(qc->keys, ctx->level, 1)) { + continue; + } + + qc->error_level = ctx->level; + (void) ngx_quic_send_cc(c); + + if (rc == NGX_OK) { + ngx_add_timer(&qc->close, 3 * ngx_quic_pto(c, ctx)); + } + } + } + + qc->closing = 1; + } + + if (rc == NGX_ERROR && qc->close.timer_set) { + /* do not wait for timer in case of fatal error */ + ngx_del_timer(&qc->close); + } + + if (ngx_quic_close_streams(c, qc) == NGX_AGAIN) { + return; + } + + if (qc->push.timer_set) { + ngx_del_timer(&qc->push); + } + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + if (qc->path_validation.timer_set) { + ngx_del_timer(&qc->path_validation); + } + + if (qc->push.posted) { + ngx_delete_posted_event(&qc->push); + } + + if (qc->key_update.posted) { + ngx_delete_posted_event(&qc->key_update); + } + + if (qc->close.timer_set) { + return; + } + + if (qc->close.posted) { + ngx_delete_posted_event(&qc->close); + } + + ngx_quic_close_sockets(c); + + ngx_quic_keys_cleanup(qc->keys); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic close completed"); + + /* may be tested from SSL callback during SSL shutdown */ + c->udp = NULL; + +quic_done: + + if (c->ssl) { + (void) ngx_ssl_shutdown(c); + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +void +ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->closing) { + return; + } + + qc->error = err; + qc->error_reason = reason; + qc->error_app = 1; + qc->error_ftype = 0; + + ngx_post_event(&qc->close, &ngx_posted_events); +} + + +void +ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + qc->shutdown = 1; + qc->shutdown_code = err; + qc->shutdown_reason = reason; + + ngx_quic_shutdown_quic(c); +} + + +static void +ngx_quic_close_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic close handler"); + + c = ev->data; + + ngx_quic_close_connection(c, NGX_OK); +} + + +static ngx_int_t +ngx_quic_handle_datagram(ngx_connection_t *c, ngx_buf_t *b, + ngx_quic_conf_t *conf) +{ + size_t size; + u_char *p, *start; + ngx_int_t rc; + ngx_uint_t good; + ngx_quic_path_t *path; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; + + good = 0; + path = NULL; + + size = b->last - b->pos; + + p = start = b->pos; + + while (p < b->last) { + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.raw = b; + pkt.data = p; + pkt.len = b->last - p; + pkt.log = c->log; + pkt.first = (p == start) ? 1 : 0; + pkt.path = path; + pkt.flags = p[0]; + pkt.raw->pos++; + + rc = ngx_quic_handle_packet(c, conf, &pkt); + +#if (NGX_DEBUG) + if (pkt.parsed) { + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet done rc:%i level:%s" + " decr:%d pn:%L perr:%ui", + rc, ngx_quic_level_name(pkt.level), + pkt.decrypted, pkt.pn, pkt.error); + } else { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet done rc:%i parse failed", rc); + } +#endif + + if (rc == NGX_ERROR || rc == NGX_DONE) { + return rc; + } + + if (rc == NGX_OK) { + good = 1; + } + + path = pkt.path; /* preserve packet path from 1st packet */ + + /* NGX_OK || NGX_DECLINED */ + + /* + * we get NGX_DECLINED when there are no keys [yet] available + * to decrypt packet. + * Instead of queueing it, we ignore it and rely on the sender's + * retransmission: + * + * RFC 9000, 12.2. Coalescing Packets + * + * For example, if decryption fails (because the keys are + * not available or for any other reason), the receiver MAY either + * discard or buffer the packet for later processing and MUST + * attempt to process the remaining packets. + * + * We also skip packets that don't match connection state + * or cannot be parsed properly. + */ + + /* b->pos is at header end, adjust by actual packet length */ + b->pos = pkt.data + pkt.len; + + p = b->pos; + } + + if (!good) { + return NGX_DONE; + } + + qc = ngx_quic_get_connection(c); + + if (qc) { + qc->received += size; + + if ((uint64_t) (c->sent + qc->received) / 8 > + (qc->streams.sent + qc->streams.recv_last) + 1048576) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected"); + + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "QUIC flood detected"; + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_handle_packet(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + ngx_int_t rc; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + c->log->action = "parsing quic packet"; + + rc = ngx_quic_parse_packet(pkt); + + if (rc == NGX_ERROR) { + return NGX_DECLINED; + } + + pkt->parsed = 1; + + c->log->action = "handling quic packet"; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rx dcid len:%uz %xV", + pkt->dcid.len, &pkt->dcid); + +#if (NGX_DEBUG) + if (pkt->level != ssl_encryption_application) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet rx scid len:%uz %xV", + pkt->scid.len, &pkt->scid); + } + + if (pkt->level == ssl_encryption_initial) { + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic address validation token len:%uz %xV", + pkt->token.len, &pkt->token); + } +#endif + + qc = ngx_quic_get_connection(c); + + if (qc) { + + if (rc == NGX_ABORT) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported version: 0x%xD", pkt->version); + return NGX_DECLINED; + } + + if (pkt->level != ssl_encryption_application) { + + if (pkt->version != qc->version) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic version mismatch: 0x%xD", pkt->version); + return NGX_DECLINED; + } + + if (pkt->first) { + qsock = ngx_quic_get_socket(c); + + if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen, + qc->path->sockaddr, qc->path->socklen, 1) + != NGX_OK) + { + /* packet comes from unknown path, possibly migration */ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic too early migration attempt"); + return NGX_DONE; + } + } + + if (ngx_quic_check_csid(qc, pkt) != NGX_OK) { + return NGX_DECLINED; + } + + } + + rc = ngx_quic_handle_payload(c, pkt); + + if (rc == NGX_DECLINED && pkt->level == ssl_encryption_application) { + if (ngx_quic_handle_stateless_reset(c, pkt) == NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic stateless reset packet detected"); + + qc->draining = 1; + ngx_post_event(&qc->close, &ngx_posted_events); + + return NGX_OK; + } + } + + return rc; + } + + /* packet does not belong to a connection */ + + if (rc == NGX_ABORT) { + return ngx_quic_negotiate_version(c, pkt); + } + + if (pkt->level == ssl_encryption_application) { + return ngx_quic_send_stateless_reset(c, conf, pkt); + } + + if (pkt->level != ssl_encryption_initial) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic expected initial, got handshake"); + return NGX_ERROR; + } + + c->log->action = "handling initial packet"; + + if (pkt->dcid.len < NGX_QUIC_CID_LEN_MIN) { + /* RFC 9000, 7.2. Negotiating Connection IDs */ + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic too short dcid in initial" + " packet: len:%i", pkt->dcid.len); + return NGX_ERROR; + } + + /* process retry and initialize connection IDs */ + + if (pkt->token.len) { + + rc = ngx_quic_validate_token(c, conf->av_token_key, pkt); + + if (rc == NGX_ERROR) { + /* internal error */ + return NGX_ERROR; + + } else if (rc == NGX_ABORT) { + /* token cannot be decrypted */ + return ngx_quic_send_early_cc(c, pkt, + NGX_QUIC_ERR_INVALID_TOKEN, + "cannot decrypt token"); + } else if (rc == NGX_DECLINED) { + /* token is invalid */ + + if (pkt->retried) { + /* invalid address validation token */ + return ngx_quic_send_early_cc(c, pkt, + NGX_QUIC_ERR_INVALID_TOKEN, + "invalid address validation token"); + } else if (conf->retry) { + /* invalid NEW_TOKEN */ + return ngx_quic_send_retry(c, conf, pkt); + } + } + + /* NGX_OK */ + + } else if (conf->retry) { + return ngx_quic_send_retry(c, conf, pkt); + + } else { + pkt->odcid = pkt->dcid; + } + + if (ngx_terminate || ngx_exiting) { + if (conf->retry) { + return ngx_quic_send_retry(c, conf, pkt); + } + + return NGX_ERROR; + } + + c->log->action = "creating quic connection"; + + qc = ngx_quic_new_connection(c, conf, pkt); + if (qc == NULL) { + return NGX_ERROR; + } + + return ngx_quic_handle_payload(c, pkt); +} + + +static ngx_int_t +ngx_quic_handle_payload(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_int_t rc; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + qc = ngx_quic_get_connection(c); + + qc->error = (ngx_uint_t) -1; + qc->error_reason = 0; + + c->log->action = "decrypting packet"; + + if (!ngx_quic_keys_available(qc->keys, pkt->level, 0)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no %s keys, ignoring packet", + ngx_quic_level_name(pkt->level)); + return NGX_DECLINED; + } + +#if !defined (OPENSSL_IS_BORINGSSL) + /* OpenSSL provides read keys for an application level before it's ready */ + + if (pkt->level == ssl_encryption_application && !c->ssl->handshaked) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no %s keys ready, ignoring packet", + ngx_quic_level_name(pkt->level)); + return NGX_DECLINED; + } +#endif + + pkt->keys = qc->keys; + pkt->key_phase = qc->key_phase; + pkt->plaintext = buf; + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + rc = ngx_quic_decrypt(pkt, &ctx->largest_pn); + if (rc != NGX_OK) { + qc->error = pkt->error; + qc->error_reason = "failed to decrypt packet"; + return rc; + } + + pkt->decrypted = 1; + + c->log->action = "handling decrypted packet"; + + if (pkt->path == NULL) { + rc = ngx_quic_set_path(c, pkt); + if (rc != NGX_OK) { + return rc; + } + } + + if (c->ssl == NULL) { + if (ngx_quic_init_connection(c) != NGX_OK) { + return NGX_ERROR; + } + } + + if (pkt->level == ssl_encryption_handshake) { + /* + * RFC 9001, 4.9.1. Discarding Initial Keys + * + * The successful use of Handshake packets indicates + * that no more Initial packets need to be exchanged + */ + ngx_quic_discard_ctx(c, ssl_encryption_initial); + + if (!qc->path->validated) { + qc->path->validated = 1; + ngx_quic_path_dbg(c, "in handshake", qc->path); + ngx_post_event(&qc->push, &ngx_posted_events); + } + } + + if (qc->closing) { + /* + * RFC 9000, 10.2. Immediate Close + * + * ... delayed or reordered packets are properly discarded. + * + * In the closing state, an endpoint retains only enough information + * to generate a packet containing a CONNECTION_CLOSE frame and to + * identify packets as belonging to the connection. + */ + + qc->error_level = pkt->level; + qc->error = NGX_QUIC_ERR_NO_ERROR; + qc->error_reason = "connection is closing, packet discarded"; + qc->error_ftype = 0; + qc->error_app = 0; + + return ngx_quic_send_cc(c); + } + + pkt->received = ngx_current_msec; + + c->log->action = "handling payload"; + + if (pkt->level != ssl_encryption_application) { + return ngx_quic_handle_frames(c, pkt); + } + + if (!pkt->key_update) { + return ngx_quic_handle_frames(c, pkt); + } + + /* switch keys and generate next on Key Phase change */ + + qc->key_phase ^= 1; + ngx_quic_keys_switch(c, qc->keys); + + rc = ngx_quic_handle_frames(c, pkt); + if (rc != NGX_OK) { + return rc; + } + + ngx_post_event(&qc->key_update, &ngx_posted_events); + + return NGX_OK; +} + + +void +ngx_quic_discard_ctx(ngx_connection_t *c, enum ssl_encryption_level_t level) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_socket_t *qsock; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_quic_keys_available(qc->keys, level, 0) + && !ngx_quic_keys_available(qc->keys, level, 1)) + { + return; + } + + ngx_quic_keys_discard(qc->keys, level); + + qc->pto_count = 0; + + ctx = ngx_quic_get_send_ctx(qc, level); + + ngx_quic_free_buffer(c, &ctx->crypto); + + while (!ngx_queue_empty(&ctx->sent)) { + q = ngx_queue_head(&ctx->sent); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_congestion_ack(c, f); + ngx_quic_free_frame(c, f); + } + + while (!ngx_queue_empty(&ctx->frames)) { + q = ngx_queue_head(&ctx->frames); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_free_frame(c, f); + } + + if (level == ssl_encryption_initial) { + /* close temporary listener with initial dcid */ + qsock = ngx_quic_find_socket(c, NGX_QUIC_UNSET_PN); + if (qsock) { + ngx_quic_close_socket(c, qsock); + } + } + + ctx->send_ack = 0; + + ngx_quic_set_lost_timer(c); +} + + +static ngx_int_t +ngx_quic_check_csid(ngx_quic_connection_t *qc, ngx_quic_header_t *pkt) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (pkt->scid.len == cid->len + && ngx_memcmp(pkt->scid.data, cid->id, cid->len) == 0) + { + return NGX_OK; + } + } + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic unexpected quic scid"); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_quic_handle_frames(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + u_char *end, *p; + ssize_t len; + ngx_buf_t buf; + ngx_uint_t do_close, nonprobing; + ngx_chain_t chain; + ngx_quic_frame_t frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + p = pkt->payload.data; + end = p + pkt->payload.len; + + do_close = 0; + nonprobing = 0; + + while (p < end) { + + c->log->action = "parsing frames"; + + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + ngx_memzero(&buf, sizeof(ngx_buf_t)); + buf.temporary = 1; + + chain.buf = &buf; + chain.next = NULL; + frame.data = &chain; + + len = ngx_quic_parse_frame(pkt, p, end, &frame); + + if (len < 0) { + qc->error = pkt->error; + return NGX_ERROR; + } + + ngx_quic_log_frame(c->log, &frame, 0); + + c->log->action = "handling frames"; + + p += len; + + switch (frame.type) { + /* probing frames */ + case NGX_QUIC_FT_PADDING: + case NGX_QUIC_FT_PATH_CHALLENGE: + case NGX_QUIC_FT_PATH_RESPONSE: + case NGX_QUIC_FT_NEW_CONNECTION_ID: + break; + + /* non-probing frames */ + default: + nonprobing = 1; + break; + } + + switch (frame.type) { + + case NGX_QUIC_FT_ACK: + if (ngx_quic_handle_ack_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + continue; + + case NGX_QUIC_FT_PADDING: + /* no action required */ + continue; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + do_close = 1; + continue; + } + + /* got there with ack-eliciting packet */ + pkt->need_ack = 1; + + switch (frame.type) { + + case NGX_QUIC_FT_CRYPTO: + + if (ngx_quic_handle_crypto_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PING: + break; + + case NGX_QUIC_FT_STREAM: + + if (ngx_quic_handle_stream_frame(c, pkt, &frame) != NGX_OK) { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_MAX_DATA: + + if (ngx_quic_handle_max_data_frame(c, &frame.u.max_data) != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + + if (ngx_quic_handle_streams_blocked_frame(c, pkt, + &frame.u.streams_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_DATA_BLOCKED: + + if (ngx_quic_handle_data_blocked_frame(c, pkt, + &frame.u.data_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + + if (ngx_quic_handle_stream_data_blocked_frame(c, pkt, + &frame.u.stream_data_blocked) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + + if (ngx_quic_handle_max_stream_data_frame(c, pkt, + &frame.u.max_stream_data) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_RESET_STREAM: + + if (ngx_quic_handle_reset_stream_frame(c, pkt, + &frame.u.reset_stream) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_STOP_SENDING: + + if (ngx_quic_handle_stop_sending_frame(c, pkt, + &frame.u.stop_sending) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + + if (ngx_quic_handle_max_streams_frame(c, pkt, &frame.u.max_streams) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + + if (ngx_quic_handle_path_challenge_frame(c, pkt, + &frame.u.path_challenge) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + + if (ngx_quic_handle_path_response_frame(c, &frame.u.path_response) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + + if (ngx_quic_handle_new_connection_id_frame(c, &frame.u.ncid) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + + if (ngx_quic_handle_retire_connection_id_frame(c, + &frame.u.retire_cid) + != NGX_OK) + { + return NGX_ERROR; + } + + break; + + default: + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic missing frame handler"); + return NGX_ERROR; + } + } + + if (p != end) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic trailing garbage in payload:%ui bytes", end - p); + + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + return NGX_ERROR; + } + + if (do_close) { + qc->draining = 1; + ngx_post_event(&qc->close, &ngx_posted_events); + } + + if (pkt->path != qc->path && nonprobing) { + + /* + * RFC 9000, 9.2. Initiating Connection Migration + * + * An endpoint can migrate a connection to a new local + * address by sending packets containing non-probing frames + * from that address. + */ + if (ngx_quic_handle_migration(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + } + + if (ngx_quic_ack_packet(c, pkt) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_quic_push_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic push handler"); + + c = ev->data; + + if (ngx_quic_output(c) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + ngx_quic_connstate_dbg(c); +} + + +void +ngx_quic_shutdown_quic(ngx_connection_t *c) +{ + ngx_quic_connection_t *qc; + + if (c->reusable) { + qc = ngx_quic_get_connection(c); + ngx_quic_finalize_connection(c, qc->shutdown_code, qc->shutdown_reason); + } +} diff --git a/src/event/quic/ngx_event_quic.h b/src/event/quic/ngx_event_quic.h new file mode 100644 index 0000000..1520167 --- /dev/null +++ b/src/event/quic/ngx_event_quic.h @@ -0,0 +1,129 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_H_INCLUDED_ +#define _NGX_EVENT_QUIC_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +#define NGX_QUIC_MAX_UDP_PAYLOAD_SIZE 65527 + +#define NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT 3 +#define NGX_QUIC_DEFAULT_MAX_ACK_DELAY 25 +#define NGX_QUIC_DEFAULT_HOST_KEY_LEN 32 +#define NGX_QUIC_SR_KEY_LEN 32 +#define NGX_QUIC_AV_KEY_LEN 32 + +#define NGX_QUIC_SR_TOKEN_LEN 16 + +#define NGX_QUIC_MIN_INITIAL_SIZE 1200 + +#define NGX_QUIC_STREAM_SERVER_INITIATED 0x01 +#define NGX_QUIC_STREAM_UNIDIRECTIONAL 0x02 + + +typedef ngx_int_t (*ngx_quic_init_pt)(ngx_connection_t *c); +typedef void (*ngx_quic_shutdown_pt)(ngx_connection_t *c); + + +typedef enum { + NGX_QUIC_STREAM_SEND_READY = 0, + NGX_QUIC_STREAM_SEND_SEND, + NGX_QUIC_STREAM_SEND_DATA_SENT, + NGX_QUIC_STREAM_SEND_DATA_RECVD, + NGX_QUIC_STREAM_SEND_RESET_SENT, + NGX_QUIC_STREAM_SEND_RESET_RECVD +} ngx_quic_stream_send_state_e; + + +typedef enum { + NGX_QUIC_STREAM_RECV_RECV = 0, + NGX_QUIC_STREAM_RECV_SIZE_KNOWN, + NGX_QUIC_STREAM_RECV_DATA_RECVD, + NGX_QUIC_STREAM_RECV_DATA_READ, + NGX_QUIC_STREAM_RECV_RESET_RECVD, + NGX_QUIC_STREAM_RECV_RESET_READ +} ngx_quic_stream_recv_state_e; + + +typedef struct { + uint64_t size; + uint64_t offset; + uint64_t last_offset; + ngx_chain_t *chain; + ngx_chain_t *last_chain; +} ngx_quic_buffer_t; + + +typedef struct { + ngx_ssl_t *ssl; + + ngx_flag_t retry; + ngx_flag_t gso_enabled; + ngx_flag_t disable_active_migration; + ngx_msec_t handshake_timeout; + ngx_msec_t idle_timeout; + ngx_str_t host_key; + size_t stream_buffer_size; + ngx_uint_t max_concurrent_streams_bidi; + ngx_uint_t max_concurrent_streams_uni; + ngx_uint_t active_connection_id_limit; + ngx_int_t stream_close_code; + ngx_int_t stream_reject_code_uni; + ngx_int_t stream_reject_code_bidi; + + ngx_quic_init_pt init; + ngx_quic_shutdown_pt shutdown; + + u_char av_token_key[NGX_QUIC_AV_KEY_LEN]; + u_char sr_token_key[NGX_QUIC_SR_KEY_LEN]; +} ngx_quic_conf_t; + + +struct ngx_quic_stream_s { + ngx_rbtree_node_t node; + ngx_queue_t queue; + ngx_connection_t *parent; + ngx_connection_t *connection; + uint64_t id; + uint64_t sent; + uint64_t acked; + uint64_t send_max_data; + uint64_t send_offset; + uint64_t send_final_size; + uint64_t recv_max_data; + uint64_t recv_offset; + uint64_t recv_window; + uint64_t recv_last; + uint64_t recv_final_size; + ngx_quic_buffer_t send; + ngx_quic_buffer_t recv; + ngx_quic_stream_send_state_e send_state; + ngx_quic_stream_recv_state_e recv_state; + unsigned cancelable:1; + unsigned fin_acked:1; +}; + + +void ngx_quic_recvmsg(ngx_event_t *ev); +void ngx_quic_run(ngx_connection_t *c, ngx_quic_conf_t *conf); +ngx_connection_t *ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi); +void ngx_quic_finalize_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason); +void ngx_quic_shutdown_connection(ngx_connection_t *c, ngx_uint_t err, + const char *reason); +ngx_int_t ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err); +ngx_int_t ngx_quic_shutdown_stream(ngx_connection_t *c, int how); +void ngx_quic_cancelable_stream(ngx_connection_t *c); +ngx_int_t ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t len, + ngx_str_t *dcid); +ngx_int_t ngx_quic_derive_key(ngx_log_t *log, const char *label, + ngx_str_t *secret, ngx_str_t *salt, u_char *out, size_t len); + +#endif /* _NGX_EVENT_QUIC_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_ack.c b/src/event/quic/ngx_event_quic_ack.c new file mode 100644 index 0000000..c7ffd44 --- /dev/null +++ b/src/event/quic/ngx_event_quic_ack.c @@ -0,0 +1,1188 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + + +#define NGX_QUIC_MAX_ACK_GAP 2 + +/* RFC 9002, 6.1.1. Packet Threshold: kPacketThreshold */ +#define NGX_QUIC_PKT_THR 3 /* packets */ +/* RFC 9002, 6.1.2. Time Threshold: kGranularity */ +#define NGX_QUIC_TIME_GRANULARITY 1 /* ms */ + +/* RFC 9002, 7.6.1. Duration: kPersistentCongestionThreshold */ +#define NGX_QUIC_PERSISTENT_CONGESTION_THR 3 + + +/* send time of ACK'ed packets */ +typedef struct { + ngx_msec_t max_pn; + ngx_msec_t oldest; + ngx_msec_t newest; +} ngx_quic_ack_stat_t; + + +static ngx_inline ngx_msec_t ngx_quic_lost_threshold(ngx_quic_connection_t *qc); +static void ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, + enum ssl_encryption_level_t level, ngx_msec_t send_time); +static ngx_int_t ngx_quic_handle_ack_frame_range(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t min, uint64_t max, + ngx_quic_ack_stat_t *st); +static void ngx_quic_drop_ack_ranges(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t pn); +static ngx_int_t ngx_quic_detect_lost(ngx_connection_t *c, + ngx_quic_ack_stat_t *st); +static ngx_msec_t ngx_quic_pcg_duration(ngx_connection_t *c); +static void ngx_quic_persistent_congestion(ngx_connection_t *c); +static void ngx_quic_congestion_lost(ngx_connection_t *c, + ngx_quic_frame_t *frame); +static void ngx_quic_lost_handler(ngx_event_t *ev); + + +/* RFC 9002, 6.1.2. Time Threshold: kTimeThreshold, kGranularity */ +static ngx_inline ngx_msec_t +ngx_quic_lost_threshold(ngx_quic_connection_t *qc) +{ + ngx_msec_t thr; + + thr = ngx_max(qc->latest_rtt, qc->avg_rtt); + thr += thr >> 3; + + return ngx_max(thr, NGX_QUIC_TIME_GRANULARITY); +} + + +ngx_int_t +ngx_quic_handle_ack_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *f) +{ + ssize_t n; + u_char *pos, *end; + uint64_t min, max, gap, range; + ngx_uint_t i; + ngx_quic_ack_stat_t send_time; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_frame_t *ack; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_handle_ack_frame level:%d", pkt->level); + + ack = &f->u.ack; + + /* + * RFC 9000, 19.3.1. ACK Ranges + * + * If any computed packet number is negative, an endpoint MUST + * generate a connection error of type FRAME_ENCODING_ERROR. + */ + + if (ack->first_range > ack->largest) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid first range in ack frame"); + return NGX_ERROR; + } + + min = ack->largest - ack->first_range; + max = ack->largest; + + send_time.oldest = NGX_TIMER_INFINITE; + send_time.newest = NGX_TIMER_INFINITE; + + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) + != NGX_OK) + { + return NGX_ERROR; + } + + /* RFC 9000, 13.2.4. Limiting Ranges by Tracking ACK Frames */ + if (ctx->largest_ack < max || ctx->largest_ack == NGX_QUIC_UNSET_PN) { + ctx->largest_ack = max; + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic updated largest received ack:%uL", max); + + /* + * RFC 9002, 5.1. Generating RTT Samples + * + * An endpoint generates an RTT sample on receiving an + * ACK frame that meets the following two conditions: + * + * - the largest acknowledged packet number is newly acknowledged + * - at least one of the newly acknowledged packets was ack-eliciting. + */ + + if (send_time.max_pn != NGX_TIMER_INFINITE) { + ngx_quic_rtt_sample(c, ack, pkt->level, send_time.max_pn); + } + } + + if (f->data) { + pos = f->data->buf->pos; + end = f->data->buf->last; + + } else { + pos = NULL; + end = NULL; + } + + for (i = 0; i < ack->range_count; i++) { + + n = ngx_quic_parse_ack_range(pkt->log, pos, end, &gap, &range); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + pos += n; + + if (gap + 2 > min) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid range:%ui in ack frame", i); + return NGX_ERROR; + } + + max = min - gap - 2; + + if (range > max) { + qc->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic invalid range:%ui in ack frame", i); + return NGX_ERROR; + } + + min = max - range; + + if (ngx_quic_handle_ack_frame_range(c, ctx, min, max, &send_time) + != NGX_OK) + { + return NGX_ERROR; + } + } + + return ngx_quic_detect_lost(c, &send_time); +} + + +static void +ngx_quic_rtt_sample(ngx_connection_t *c, ngx_quic_ack_frame_t *ack, + enum ssl_encryption_level_t level, ngx_msec_t send_time) +{ + ngx_msec_t latest_rtt, ack_delay, adjusted_rtt, rttvar_sample; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + latest_rtt = ngx_current_msec - send_time; + qc->latest_rtt = latest_rtt; + + if (qc->min_rtt == NGX_TIMER_INFINITE) { + qc->min_rtt = latest_rtt; + qc->avg_rtt = latest_rtt; + qc->rttvar = latest_rtt / 2; + qc->first_rtt = ngx_current_msec; + + } else { + qc->min_rtt = ngx_min(qc->min_rtt, latest_rtt); + + ack_delay = (ack->delay << qc->ctp.ack_delay_exponent) / 1000; + + if (c->ssl->handshaked) { + ack_delay = ngx_min(ack_delay, qc->ctp.max_ack_delay); + } + + adjusted_rtt = latest_rtt; + + if (qc->min_rtt + ack_delay < latest_rtt) { + adjusted_rtt -= ack_delay; + } + + rttvar_sample = ngx_abs((ngx_msec_int_t) (qc->avg_rtt - adjusted_rtt)); + qc->rttvar += (rttvar_sample >> 2) - (qc->rttvar >> 2); + qc->avg_rtt += (adjusted_rtt >> 3) - (qc->avg_rtt >> 3); + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic rtt sample latest:%M min:%M avg:%M var:%M", + latest_rtt, qc->min_rtt, qc->avg_rtt, qc->rttvar); +} + + +static ngx_int_t +ngx_quic_handle_ack_frame_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t min, uint64_t max, ngx_quic_ack_stat_t *st) +{ + ngx_uint_t found; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (ctx->level == ssl_encryption_application) { + if (ngx_quic_handle_path_mtu(c, qc->path, min, max) != NGX_OK) { + return NGX_ERROR; + } + } + + st->max_pn = NGX_TIMER_INFINITE; + found = 0; + + q = ngx_queue_head(&ctx->sent); + + while (q != ngx_queue_sentinel(&ctx->sent)) { + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + q = ngx_queue_next(q); + + if (f->pnum > max) { + break; + } + + if (f->pnum >= min) { + ngx_quic_congestion_ack(c, f); + + switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + ngx_quic_drop_ack_ranges(c, ctx, f->u.ack.largest); + break; + + case NGX_QUIC_FT_STREAM: + case NGX_QUIC_FT_RESET_STREAM: + ngx_quic_handle_stream_ack(c, f); + break; + } + + if (f->pnum == max) { + st->max_pn = f->send_time; + } + + /* save earliest and latest send times of frames ack'ed */ + if (st->oldest == NGX_TIMER_INFINITE || f->send_time < st->oldest) { + st->oldest = f->send_time; + } + + if (st->newest == NGX_TIMER_INFINITE || f->send_time > st->newest) { + st->newest = f->send_time; + } + + ngx_queue_remove(&f->queue); + ngx_quic_free_frame(c, f); + found = 1; + } + } + + if (!found) { + + if (max < ctx->pnum) { + /* duplicate ACK or ACK for non-ack-eliciting frame */ + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic ACK for the packet not sent"); + + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_ftype = NGX_QUIC_FT_ACK; + qc->error_reason = "unknown packet number"; + + return NGX_ERROR; + } + + if (!qc->push.timer_set) { + ngx_post_event(&qc->push, &ngx_posted_events); + } + + qc->pto_count = 0; + + return NGX_OK; +} + + +void +ngx_quic_congestion_ack(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + ngx_uint_t blocked; + ngx_msec_t timer; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + if (f->plen == 0) { + return; + } + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + if (f->pnum < qc->rst_pnum) { + return; + } + + blocked = (cg->in_flight >= cg->window) ? 1 : 0; + + cg->in_flight -= f->plen; + + timer = f->send_time - cg->recovery_start; + + if ((ngx_msec_int_t) timer <= 0) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion ack recovery win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + goto done; + } + + if (cg->window < cg->ssthresh) { + cg->window += f->plen; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion slow start win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + } else { + cg->window += qc->tp.max_udp_payload_size * f->plen / cg->window; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion avoidance win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + } + + /* prevent recovery_start from wrapping */ + + timer = cg->recovery_start - ngx_current_msec + qc->tp.max_idle_timeout * 2; + + if ((ngx_msec_int_t) timer < 0) { + cg->recovery_start = ngx_current_msec - qc->tp.max_idle_timeout * 2; + } + +done: + + if (blocked && cg->in_flight < cg->window) { + ngx_post_event(&qc->push, &ngx_posted_events); + } +} + + +static void +ngx_quic_drop_ack_ranges(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pn) +{ + uint64_t base; + ngx_uint_t i, smallest, largest; + ngx_quic_ack_range_t *r; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_drop_ack_ranges pn:%uL largest:%uL" + " fr:%uL nranges:%ui", pn, ctx->largest_range, + ctx->first_range, ctx->nranges); + + base = ctx->largest_range; + + if (base == NGX_QUIC_UNSET_PN) { + return; + } + + if (ctx->pending_ack != NGX_QUIC_UNSET_PN && pn >= ctx->pending_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn >= largest) { + ctx->largest_range = NGX_QUIC_UNSET_PN; + ctx->first_range = 0; + ctx->nranges = 0; + return; + } + + if (pn >= smallest) { + ctx->first_range = largest - pn - 1; + ctx->nranges = 0; + return; + } + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + largest = smallest - r->gap - 2; + smallest = largest - r->range; + + if (pn >= largest) { + ctx->nranges = i; + return; + } + if (pn >= smallest) { + r->range = largest - pn - 1; + ctx->nranges = i + 1; + return; + } + } +} + + +static ngx_int_t +ngx_quic_detect_lost(ngx_connection_t *c, ngx_quic_ack_stat_t *st) +{ + ngx_uint_t i, nlost; + ngx_msec_t now, wait, thr, oldest, newest; + ngx_queue_t *q; + ngx_quic_frame_t *start; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + thr = ngx_quic_lost_threshold(qc); + + /* send time of lost packets across all send contexts */ + oldest = NGX_TIMER_INFINITE; + newest = NGX_TIMER_INFINITE; + + nlost = 0; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ctx->largest_ack == NGX_QUIC_UNSET_PN) { + continue; + } + + while (!ngx_queue_empty(&ctx->sent)) { + + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (start->pnum > ctx->largest_ack) { + break; + } + + wait = start->send_time + thr - now; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic detect_lost pnum:%uL thr:%M wait:%i level:%d", + start->pnum, thr, (ngx_int_t) wait, start->level); + + if ((ngx_msec_int_t) wait > 0 + && ctx->largest_ack - start->pnum < NGX_QUIC_PKT_THR) + { + break; + } + + if (start->send_time > qc->first_rtt) { + + if (oldest == NGX_TIMER_INFINITE || start->send_time < oldest) { + oldest = start->send_time; + } + + if (newest == NGX_TIMER_INFINITE || start->send_time > newest) { + newest = start->send_time; + } + + nlost++; + } + + ngx_quic_resend_frames(c, ctx); + } + } + + + /* RFC 9002, 7.6.2. Establishing Persistent Congestion */ + + /* + * Once acknowledged, packets are no longer tracked. Thus no send time + * information is available for such packets. This limits persistent + * congestion algorithm to packets mentioned within ACK ranges of the + * latest ACK frame. + */ + + if (st && nlost >= 2 && (st->newest < oldest || st->oldest > newest)) { + + if (newest - oldest > ngx_quic_pcg_duration(c)) { + ngx_quic_persistent_congestion(c); + } + } + + ngx_quic_set_lost_timer(c); + + return NGX_OK; +} + + +static ngx_msec_t +ngx_quic_pcg_duration(ngx_connection_t *c) +{ + ngx_msec_t duration; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + duration = qc->avg_rtt; + duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); + duration += qc->ctp.max_ack_delay; + duration *= NGX_QUIC_PERSISTENT_CONGESTION_THR; + + return duration; +} + + +static void +ngx_quic_persistent_congestion(ngx_connection_t *c) +{ + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + cg->recovery_start = ngx_current_msec; + cg->window = qc->tp.max_udp_payload_size * 2; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic persistent congestion win:%uz", cg->window); +} + + +void +ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + uint64_t pnum; + ngx_queue_t *q; + ngx_quic_frame_t *f, *start; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + q = ngx_queue_head(&ctx->sent); + start = ngx_queue_data(q, ngx_quic_frame_t, queue); + pnum = start->pnum; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic resend packet pnum:%uL", start->pnum); + + ngx_quic_congestion_lost(c, start); + + do { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->pnum != pnum) { + break; + } + + q = ngx_queue_next(q); + + ngx_queue_remove(&f->queue); + + switch (f->type) { + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + if (ctx->level == ssl_encryption_application) { + /* force generation of most recent acknowledgment */ + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + ngx_quic_free_frame(c, f); + break; + + case NGX_QUIC_FT_PING: + case NGX_QUIC_FT_PATH_CHALLENGE: + case NGX_QUIC_FT_PATH_RESPONSE: + case NGX_QUIC_FT_CONNECTION_CLOSE: + ngx_quic_free_frame(c, f); + break; + + case NGX_QUIC_FT_MAX_DATA: + f->u.max_data.max_data = qc->streams.recv_max_data; + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + f->u.max_streams.limit = f->u.max_streams.bidi + ? qc->streams.client_max_streams_bidi + : qc->streams.client_max_streams_uni; + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + qs = ngx_quic_find_stream(&qc->streams.tree, + f->u.max_stream_data.id); + if (qs == NULL) { + ngx_quic_free_frame(c, f); + break; + } + + f->u.max_stream_data.limit = qs->recv_max_data; + ngx_quic_queue_frame(qc, f); + break; + + case NGX_QUIC_FT_STREAM: + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + + if (qs) { + if (qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT + || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD) + { + ngx_quic_free_frame(c, f); + break; + } + } + + /* fall through */ + + default: + ngx_queue_insert_tail(&ctx->frames, &f->queue); + } + + } while (q != ngx_queue_sentinel(&ctx->sent)); + + if (qc->closing) { + return; + } + + ngx_post_event(&qc->push, &ngx_posted_events); +} + + +static void +ngx_quic_congestion_lost(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + ngx_uint_t blocked; + ngx_msec_t timer; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + if (f->plen == 0) { + return; + } + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + if (f->pnum < qc->rst_pnum) { + return; + } + + blocked = (cg->in_flight >= cg->window) ? 1 : 0; + + cg->in_flight -= f->plen; + f->plen = 0; + + timer = f->send_time - cg->recovery_start; + + if ((ngx_msec_int_t) timer <= 0) { + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion lost recovery win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + + goto done; + } + + cg->recovery_start = ngx_current_msec; + cg->window /= 2; + + if (cg->window < qc->tp.max_udp_payload_size * 2) { + cg->window = qc->tp.max_udp_payload_size * 2; + } + + cg->ssthresh = cg->window; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion lost win:%uz ss:%z if:%uz", + cg->window, cg->ssthresh, cg->in_flight); + +done: + + if (blocked && cg->in_flight < cg->window) { + ngx_post_event(&qc->push, &ngx_posted_events); + } +} + + +void +ngx_quic_set_lost_timer(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t lost, pto, w; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + + lost = -1; + pto = -1; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i]; + + if (ngx_queue_empty(&ctx->sent)) { + continue; + } + + if (ctx->largest_ack != NGX_QUIC_UNSET_PN) { + q = ngx_queue_head(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) + (f->send_time + ngx_quic_lost_threshold(qc) - now); + + if (f->pnum <= ctx->largest_ack) { + if (w < 0 || ctx->largest_ack - f->pnum >= NGX_QUIC_PKT_THR) { + w = 0; + } + + if (lost == -1 || w < lost) { + lost = w; + } + } + } + + q = ngx_queue_last(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) + (f->send_time + (ngx_quic_pto(c, ctx) << qc->pto_count) - now); + + if (w < 0) { + w = 0; + } + + if (pto == -1 || w < pto) { + pto = w; + } + } + + if (qc->pto.timer_set) { + ngx_del_timer(&qc->pto); + } + + if (lost != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic lost timer lost:%M", lost); + + qc->pto.handler = ngx_quic_lost_handler; + ngx_add_timer(&qc->pto, lost); + return; + } + + if (pto != -1) { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic lost timer pto:%M", pto); + + qc->pto.handler = ngx_quic_pto_handler; + ngx_add_timer(&qc->pto, pto); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic lost timer unset"); +} + + +ngx_msec_t +ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_msec_t duration; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* RFC 9002, Appendix A.8. Setting the Loss Detection Timer */ + + duration = qc->avg_rtt; + duration += ngx_max(4 * qc->rttvar, NGX_QUIC_TIME_GRANULARITY); + + if (ctx->level == ssl_encryption_application && c->ssl->handshaked) { + duration += qc->ctp.max_ack_delay; + } + + return duration; +} + + +static +void ngx_quic_lost_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic lost timer"); + + c = ev->data; + + if (ngx_quic_detect_lost(c, NULL) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + return; + } + + ngx_quic_connstate_dbg(c); +} + + +void +ngx_quic_pto_handler(ngx_event_t *ev) +{ + ngx_uint_t i, n; + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t w; + ngx_connection_t *c; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, 0, "quic pto timer"); + + c = ev->data; + qc = ngx_quic_get_connection(c); + now = ngx_current_msec; + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + if (ngx_queue_empty(&ctx->sent)) { + continue; + } + + q = ngx_queue_last(&ctx->sent); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + w = (ngx_msec_int_t) + (f->send_time + (ngx_quic_pto(c, ctx) << qc->pto_count) - now); + + if (f->pnum <= ctx->largest_ack + && ctx->largest_ack != NGX_QUIC_UNSET_PN) + { + continue; + } + + if (w > 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic pto %s pto_count:%ui", + ngx_quic_level_name(ctx->level), qc->pto_count); + + for (n = 0; n < 2; n++) { + + f = ngx_quic_alloc_frame(c); + if (f == NULL) { + goto failed; + } + + f->level = ctx->level; + f->type = NGX_QUIC_FT_PING; + f->ignore_congestion = 1; + + if (ngx_quic_frame_sendto(c, f, 0, qc->path) == NGX_ERROR) { + goto failed; + } + } + } + + qc->pto_count++; + + ngx_quic_set_lost_timer(c); + + ngx_quic_connstate_dbg(c); + + return; + +failed: + + ngx_quic_close_connection(c, NGX_ERROR); + return; +} + + +ngx_int_t +ngx_quic_ack_packet(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + uint64_t base, largest, smallest, gs, ge, gap, range, pn; + uint64_t prev_pending; + ngx_uint_t i, nr; + ngx_quic_send_ctx_t *ctx; + ngx_quic_ack_range_t *r; + ngx_quic_connection_t *qc; + + c->log->action = "preparing ack"; + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_ack_packet pn:%uL largest %L fr:%uL" + " nranges:%ui", pkt->pn, (int64_t) ctx->largest_range, + ctx->first_range, ctx->nranges); + + if (!ngx_quic_keys_available(qc->keys, ctx->level, 1)) { + return NGX_OK; + } + + prev_pending = ctx->pending_ack; + + if (pkt->need_ack) { + + ngx_post_event(&qc->push, &ngx_posted_events); + + if (ctx->send_ack == 0) { + ctx->ack_delay_start = ngx_current_msec; + } + + ctx->send_ack++; + + if (ctx->pending_ack == NGX_QUIC_UNSET_PN + || ctx->pending_ack < pkt->pn) + { + ctx->pending_ack = pkt->pn; + } + } + + base = ctx->largest_range; + pn = pkt->pn; + + if (base == NGX_QUIC_UNSET_PN) { + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + return NGX_OK; + } + + if (base == pn) { + return NGX_OK; + } + + largest = base; + smallest = largest - ctx->first_range; + + if (pn > base) { + + if (pn - base == 1) { + ctx->first_range++; + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + + return NGX_OK; + + } else { + /* new gap in front of current largest */ + + /* no place for new range, send current range as is */ + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + + if (prev_pending != NGX_QUIC_UNSET_PN) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (prev_pending == ctx->pending_ack || !pkt->need_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + } + + gap = pn - base - 2; + range = ctx->first_range; + + ctx->first_range = 0; + ctx->largest_range = pn; + ctx->largest_received = pkt->received; + + /* packet is out of order, force send */ + if (pkt->need_ack) { + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + i = 0; + + goto insert; + } + } + + /* pn < base, perform lookup in existing ranges */ + + /* packet is out of order */ + if (pkt->need_ack) { + ctx->send_ack = NGX_QUIC_MAX_ACK_GAP; + } + + if (pn >= smallest && pn <= largest) { + return NGX_OK; + } + +#if (NGX_SUPPRESS_WARN) + r = NULL; +#endif + + for (i = 0; i < ctx->nranges; i++) { + r = &ctx->ranges[i]; + + ge = smallest - 1; + gs = ge - r->gap; + + if (pn >= gs && pn <= ge) { + + if (gs == ge) { + /* gap size is exactly one packet, now filled */ + + /* data moves to previous range, current is removed */ + + if (i == 0) { + ctx->first_range += r->range + 2; + + } else { + ctx->ranges[i - 1].range += r->range + 2; + } + + nr = ctx->nranges - i - 1; + if (nr) { + ngx_memmove(&ctx->ranges[i], &ctx->ranges[i + 1], + sizeof(ngx_quic_ack_range_t) * nr); + } + + ctx->nranges--; + + } else if (pn == gs) { + /* current gap shrinks from tail (current range grows) */ + r->gap--; + r->range++; + + } else if (pn == ge) { + /* current gap shrinks from head (previous range grows) */ + r->gap--; + + if (i == 0) { + ctx->first_range++; + + } else { + ctx->ranges[i - 1].range++; + } + + } else { + /* current gap is split into two parts */ + + gap = ge - pn - 1; + range = 0; + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + if (prev_pending != NGX_QUIC_UNSET_PN) { + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + } + + if (prev_pending == ctx->pending_ack || !pkt->need_ack) { + ctx->pending_ack = NGX_QUIC_UNSET_PN; + } + } + + r->gap = pn - gs - 1; + goto insert; + } + + return NGX_OK; + } + + largest = smallest - r->gap - 2; + smallest = largest - r->range; + + if (pn >= smallest && pn <= largest) { + /* this packet number is already known */ + return NGX_OK; + } + + } + + if (pn == smallest - 1) { + /* extend first or last range */ + + if (i == 0) { + ctx->first_range++; + + } else { + r->range++; + } + + return NGX_OK; + } + + /* nothing found, add new range at the tail */ + + if (ctx->nranges == NGX_QUIC_MAX_RANGES) { + /* packet is too old to keep it */ + + if (pkt->need_ack) { + return ngx_quic_send_ack_range(c, ctx, pn, pn); + } + + return NGX_OK; + } + + gap = smallest - 2 - pn; + range = 0; + +insert: + + if (ctx->nranges < NGX_QUIC_MAX_RANGES) { + ctx->nranges++; + } + + ngx_memmove(&ctx->ranges[i + 1], &ctx->ranges[i], + sizeof(ngx_quic_ack_range_t) * (ctx->nranges - i - 1)); + + ctx->ranges[i].gap = gap; + ctx->ranges[i].range = range; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_generate_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_msec_t delay; + ngx_quic_connection_t *qc; + + if (!ctx->send_ack) { + return NGX_OK; + } + + if (ctx->level == ssl_encryption_application) { + + delay = ngx_current_msec - ctx->ack_delay_start; + qc = ngx_quic_get_connection(c); + + if (ngx_queue_empty(&ctx->frames) + && ctx->send_ack < NGX_QUIC_MAX_ACK_GAP + && delay < qc->tp.max_ack_delay) + { + if (!qc->push.timer_set && !qc->closing) { + ngx_add_timer(&qc->push, + qc->tp.max_ack_delay - delay); + } + + return NGX_OK; + } + } + + if (ngx_quic_send_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + ctx->send_ack = 0; + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_ack.h b/src/event/quic/ngx_event_quic_ack.h new file mode 100644 index 0000000..56920c2 --- /dev/null +++ b/src/event/quic/ngx_event_quic_ack.h @@ -0,0 +1,30 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_ACK_H_INCLUDED_ +#define _NGX_EVENT_QUIC_ACK_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +ngx_int_t ngx_quic_handle_ack_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *f); + +void ngx_quic_congestion_ack(ngx_connection_t *c, + ngx_quic_frame_t *frame); +void ngx_quic_resend_frames(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); +void ngx_quic_set_lost_timer(ngx_connection_t *c); +void ngx_quic_pto_handler(ngx_event_t *ev); +ngx_msec_t ngx_quic_pto(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); + +ngx_int_t ngx_quic_ack_packet(ngx_connection_t *c, + ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_generate_ack(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); + +#endif /* _NGX_EVENT_QUIC_ACK_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_bpf.c b/src/event/quic/ngx_event_quic_bpf.c new file mode 100644 index 0000000..ab024ad --- /dev/null +++ b/src/event/quic/ngx_event_quic_bpf.c @@ -0,0 +1,657 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +#define NGX_QUIC_BPF_VARNAME "NGINX_BPF_MAPS" +#define NGX_QUIC_BPF_VARSEP ';' +#define NGX_QUIC_BPF_ADDRSEP '#' + + +#define ngx_quic_bpf_get_conf(cycle) \ + (ngx_quic_bpf_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_quic_bpf_module) + +#define ngx_quic_bpf_get_old_conf(cycle) \ + cycle->old_cycle->conf_ctx ? ngx_quic_bpf_get_conf(cycle->old_cycle) \ + : NULL + +#define ngx_core_get_conf(cycle) \ + (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module) + + +typedef struct { + ngx_queue_t queue; + int map_fd; + + struct sockaddr *sockaddr; + socklen_t socklen; + ngx_uint_t unused; /* unsigned unused:1; */ +} ngx_quic_sock_group_t; + + +typedef struct { + ngx_flag_t enabled; + ngx_uint_t map_size; + ngx_queue_t groups; /* of ngx_quic_sock_group_t */ +} ngx_quic_bpf_conf_t; + + +static void *ngx_quic_bpf_create_conf(ngx_cycle_t *cycle); +static ngx_int_t ngx_quic_bpf_module_init(ngx_cycle_t *cycle); + +static void ngx_quic_bpf_cleanup(void *data); +static ngx_inline void ngx_quic_bpf_close(ngx_log_t *log, int fd, + const char *name); + +static ngx_quic_sock_group_t *ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, + ngx_listening_t *ls); +static ngx_quic_sock_group_t *ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, + struct sockaddr *sa, socklen_t socklen); +static ngx_quic_sock_group_t *ngx_quic_bpf_create_group(ngx_cycle_t *cycle, + ngx_listening_t *ls); +static ngx_quic_sock_group_t *ngx_quic_bpf_get_group(ngx_cycle_t *cycle, + ngx_listening_t *ls); +static ngx_int_t ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, + ngx_listening_t *ls); +static uint64_t ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log); + +static ngx_int_t ngx_quic_bpf_export_maps(ngx_cycle_t *cycle); +static ngx_int_t ngx_quic_bpf_import_maps(ngx_cycle_t *cycle); + +extern ngx_bpf_program_t ngx_quic_reuseport_helper; + + +static ngx_command_t ngx_quic_bpf_commands[] = { + + { ngx_string("quic_bpf"), + NGX_MAIN_CONF|NGX_DIRECT_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + 0, + offsetof(ngx_quic_bpf_conf_t, enabled), + NULL }, + + ngx_null_command +}; + + +static ngx_core_module_t ngx_quic_bpf_module_ctx = { + ngx_string("quic_bpf"), + ngx_quic_bpf_create_conf, + NULL +}; + + +ngx_module_t ngx_quic_bpf_module = { + NGX_MODULE_V1, + &ngx_quic_bpf_module_ctx, /* module context */ + ngx_quic_bpf_commands, /* module directives */ + NGX_CORE_MODULE, /* module type */ + NULL, /* init master */ + ngx_quic_bpf_module_init, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void * +ngx_quic_bpf_create_conf(ngx_cycle_t *cycle) +{ + ngx_quic_bpf_conf_t *bcf; + + bcf = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_bpf_conf_t)); + if (bcf == NULL) { + return NULL; + } + + bcf->enabled = NGX_CONF_UNSET; + bcf->map_size = NGX_CONF_UNSET_UINT; + + ngx_queue_init(&bcf->groups); + + return bcf; +} + + +static ngx_int_t +ngx_quic_bpf_module_init(ngx_cycle_t *cycle) +{ + ngx_uint_t i; + ngx_listening_t *ls; + ngx_core_conf_t *ccf; + ngx_pool_cleanup_t *cln; + ngx_quic_bpf_conf_t *bcf; + + if (ngx_test_config) { + /* + * during config test, SO_REUSEPORT socket option is + * not set, thus making further processing meaningless + */ + return NGX_OK; + } + + ccf = ngx_core_get_conf(cycle); + bcf = ngx_quic_bpf_get_conf(cycle); + + ngx_conf_init_value(bcf->enabled, 0); + + bcf->map_size = ccf->worker_processes * 4; + + cln = ngx_pool_cleanup_add(cycle->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->data = bcf; + cln->handler = ngx_quic_bpf_cleanup; + + if (ngx_inherited && ngx_is_init_cycle(cycle->old_cycle)) { + if (ngx_quic_bpf_import_maps(cycle) != NGX_OK) { + goto failed; + } + } + + ls = cycle->listening.elts; + + for (i = 0; i < cycle->listening.nelts; i++) { + if (ls[i].quic && ls[i].reuseport) { + if (ngx_quic_bpf_group_add_socket(cycle, &ls[i]) != NGX_OK) { + goto failed; + } + } + } + + if (ngx_quic_bpf_export_maps(cycle) != NGX_OK) { + goto failed; + } + + return NGX_OK; + +failed: + + if (ngx_is_init_cycle(cycle->old_cycle)) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "ngx_quic_bpf_module failed to initialize, check limits"); + + /* refuse to start */ + return NGX_ERROR; + } + + /* + * returning error now will lead to master process exiting immediately + * leaving worker processes orphaned, what is really unexpected. + * Instead, just issue a not about failed initialization and try + * to cleanup a bit. Still program can be already loaded to kernel + * for some reuseport groups, and there is no way to revert, so + * behaviour may be inconsistent. + */ + + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "ngx_quic_bpf_module failed to initialize properly, ignored." + "please check limits and note that nginx state now " + "can be inconsistent and restart may be required"); + + return NGX_OK; +} + + +static void +ngx_quic_bpf_cleanup(void *data) +{ + ngx_quic_bpf_conf_t *bcf = (ngx_quic_bpf_conf_t *) data; + + ngx_queue_t *q; + ngx_quic_sock_group_t *grp; + + for (q = ngx_queue_head(&bcf->groups); + q != ngx_queue_sentinel(&bcf->groups); + q = ngx_queue_next(q)) + { + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + ngx_quic_bpf_close(ngx_cycle->log, grp->map_fd, "map"); + } +} + + +static ngx_inline void +ngx_quic_bpf_close(ngx_log_t *log, int fd, const char *name) +{ + if (close(fd) != -1) { + return; + } + + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "quic bpf close %s fd:%d failed", name, fd); +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_find_group(ngx_quic_bpf_conf_t *bcf, ngx_listening_t *ls) +{ + ngx_queue_t *q; + ngx_quic_sock_group_t *grp; + + for (q = ngx_queue_head(&bcf->groups); + q != ngx_queue_sentinel(&bcf->groups); + q = ngx_queue_next(q)) + { + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + if (ngx_cmp_sockaddr(ls->sockaddr, ls->socklen, + grp->sockaddr, grp->socklen, 1) + == NGX_OK) + { + return grp; + } + } + + return NULL; +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_alloc_group(ngx_cycle_t *cycle, struct sockaddr *sa, + socklen_t socklen) +{ + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + grp = ngx_pcalloc(cycle->pool, sizeof(ngx_quic_sock_group_t)); + if (grp == NULL) { + return NULL; + } + + grp->socklen = socklen; + grp->sockaddr = ngx_palloc(cycle->pool, socklen); + if (grp->sockaddr == NULL) { + return NULL; + } + ngx_memcpy(grp->sockaddr, sa, socklen); + + ngx_queue_insert_tail(&bcf->groups, &grp->queue); + + return grp; +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_create_group(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + int progfd, failed, flags, rc; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + if (!bcf->enabled) { + return NULL; + } + + grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen); + if (grp == NULL) { + return NULL; + } + + grp->map_fd = ngx_bpf_map_create(cycle->log, BPF_MAP_TYPE_SOCKHASH, + sizeof(uint64_t), sizeof(uint64_t), + bcf->map_size, 0); + if (grp->map_fd == -1) { + goto failed; + } + + flags = fcntl(grp->map_fd, F_GETFD); + if (flags == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, errno, + "quic bpf getfd failed"); + goto failed; + } + + /* need to inherit map during binary upgrade after exec */ + flags &= ~FD_CLOEXEC; + + rc = fcntl(grp->map_fd, F_SETFD, flags); + if (rc == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, errno, + "quic bpf setfd failed"); + goto failed; + } + + ngx_bpf_program_link(&ngx_quic_reuseport_helper, + "ngx_quic_sockmap", grp->map_fd); + + progfd = ngx_bpf_load_program(cycle->log, &ngx_quic_reuseport_helper); + if (progfd < 0) { + goto failed; + } + + failed = 0; + + if (setsockopt(ls->fd, SOL_SOCKET, SO_ATTACH_REUSEPORT_EBPF, + &progfd, sizeof(int)) + == -1) + { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_socket_errno, + "quic bpf setsockopt(SO_ATTACH_REUSEPORT_EBPF) failed"); + failed = 1; + } + + ngx_quic_bpf_close(cycle->log, progfd, "program"); + + if (failed) { + goto failed; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap created fd:%d", grp->map_fd); + return grp; + +failed: + + if (grp->map_fd != -1) { + ngx_quic_bpf_close(cycle->log, grp->map_fd, "map"); + } + + ngx_queue_remove(&grp->queue); + + return NULL; +} + + +static ngx_quic_sock_group_t * +ngx_quic_bpf_get_group(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + ngx_quic_bpf_conf_t *bcf, *old_bcf; + ngx_quic_sock_group_t *grp, *ogrp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + grp = ngx_quic_bpf_find_group(bcf, ls); + if (grp) { + return grp; + } + + old_bcf = ngx_quic_bpf_get_old_conf(cycle); + + if (old_bcf == NULL) { + return ngx_quic_bpf_create_group(cycle, ls); + } + + ogrp = ngx_quic_bpf_find_group(old_bcf, ls); + if (ogrp == NULL) { + return ngx_quic_bpf_create_group(cycle, ls); + } + + grp = ngx_quic_bpf_alloc_group(cycle, ls->sockaddr, ls->socklen); + if (grp == NULL) { + return NULL; + } + + grp->map_fd = dup(ogrp->map_fd); + if (grp->map_fd == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + "quic bpf failed to duplicate bpf map descriptor"); + + ngx_queue_remove(&grp->queue); + + return NULL; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap fd duplicated old:%d new:%d", + ogrp->map_fd, grp->map_fd); + + return grp; +} + + +static ngx_int_t +ngx_quic_bpf_group_add_socket(ngx_cycle_t *cycle, ngx_listening_t *ls) +{ + uint64_t cookie; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + bcf = ngx_quic_bpf_get_conf(cycle); + + grp = ngx_quic_bpf_get_group(cycle, ls); + + if (grp == NULL) { + if (!bcf->enabled) { + return NGX_OK; + } + + return NGX_ERROR; + } + + grp->unused = 0; + + cookie = ngx_quic_bpf_socket_key(ls->fd, cycle->log); + if (cookie == (uint64_t) NGX_ERROR) { + return NGX_ERROR; + } + + /* map[cookie] = socket; for use in kernel helper */ + if (ngx_bpf_map_update(grp->map_fd, &cookie, &ls->fd, BPF_ANY) == -1) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, ngx_errno, + "quic bpf failed to update socket map key=%xL", cookie); + return NGX_ERROR; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap fd:%d add socket:%d cookie:0x%xL worker:%ui", + grp->map_fd, ls->fd, cookie, ls->worker); + + /* do not inherit this socket */ + ls->ignore = 1; + + return NGX_OK; +} + + +static uint64_t +ngx_quic_bpf_socket_key(ngx_fd_t fd, ngx_log_t *log) +{ + uint64_t cookie; + socklen_t optlen; + + optlen = sizeof(cookie); + + if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "quic bpf getsockopt(SO_COOKIE) failed"); + + return (ngx_uint_t) NGX_ERROR; + } + + return cookie; +} + + +static ngx_int_t +ngx_quic_bpf_export_maps(ngx_cycle_t *cycle) +{ + u_char *p, *buf; + size_t len; + ngx_str_t *var; + ngx_queue_t *q; + ngx_core_conf_t *ccf; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + ccf = ngx_core_get_conf(cycle); + bcf = ngx_quic_bpf_get_conf(cycle); + + len = sizeof(NGX_QUIC_BPF_VARNAME) + 1; + + q = ngx_queue_head(&bcf->groups); + + while (q != ngx_queue_sentinel(&bcf->groups)) { + + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + q = ngx_queue_next(q); + + if (grp->unused) { + /* + * map was inherited, but it is not used in this configuration; + * do not pass such map further and drop the group to prevent + * interference with changes during reload + */ + + ngx_quic_bpf_close(cycle->log, grp->map_fd, "map"); + ngx_queue_remove(&grp->queue); + + continue; + } + + len += NGX_INT32_LEN + 1 + NGX_SOCKADDR_STRLEN + 1; + } + + len++; + + buf = ngx_palloc(cycle->pool, len); + if (buf == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(buf, NGX_QUIC_BPF_VARNAME "=", + sizeof(NGX_QUIC_BPF_VARNAME)); + + for (q = ngx_queue_head(&bcf->groups); + q != ngx_queue_sentinel(&bcf->groups); + q = ngx_queue_next(q)) + { + grp = ngx_queue_data(q, ngx_quic_sock_group_t, queue); + + p = ngx_sprintf(p, "%ud", grp->map_fd); + + *p++ = NGX_QUIC_BPF_ADDRSEP; + + p += ngx_sock_ntop(grp->sockaddr, grp->socklen, p, + NGX_SOCKADDR_STRLEN, 1); + + *p++ = NGX_QUIC_BPF_VARSEP; + } + + *p = '\0'; + + var = ngx_array_push(&ccf->env); + if (var == NULL) { + return NGX_ERROR; + } + + var->data = buf; + var->len = sizeof(NGX_QUIC_BPF_VARNAME) - 1; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_bpf_import_maps(ngx_cycle_t *cycle) +{ + int s; + u_char *inherited, *p, *v; + ngx_uint_t in_fd; + ngx_addr_t tmp; + ngx_quic_bpf_conf_t *bcf; + ngx_quic_sock_group_t *grp; + + inherited = (u_char *) getenv(NGX_QUIC_BPF_VARNAME); + + if (inherited == NULL) { + return NGX_OK; + } + + bcf = ngx_quic_bpf_get_conf(cycle); + +#if (NGX_SUPPRESS_WARN) + s = -1; +#endif + + in_fd = 1; + + for (p = inherited, v = p; *p; p++) { + + switch (*p) { + + case NGX_QUIC_BPF_ADDRSEP: + + if (!in_fd) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited env"); + return NGX_ERROR; + } + in_fd = 0; + + s = ngx_atoi(v, p - v); + if (s == NGX_ERROR) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited map fd"); + return NGX_ERROR; + } + + v = p + 1; + break; + + case NGX_QUIC_BPF_VARSEP: + + if (in_fd) { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited env"); + return NGX_ERROR; + } + in_fd = 1; + + grp = ngx_pcalloc(cycle->pool, + sizeof(ngx_quic_sock_group_t)); + if (grp == NULL) { + return NGX_ERROR; + } + + grp->map_fd = s; + + if (ngx_parse_addr_port(cycle->pool, &tmp, v, p - v) + != NGX_OK) + { + ngx_log_error(NGX_LOG_EMERG, cycle->log, 0, + "quic bpf failed to parse inherited" + " address '%*s'", p - v , v); + + ngx_quic_bpf_close(cycle->log, s, "inherited map"); + + return NGX_ERROR; + } + + grp->sockaddr = tmp.sockaddr; + grp->socklen = tmp.socklen; + + grp->unused = 1; + + ngx_queue_insert_tail(&bcf->groups, &grp->queue); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, cycle->log, 0, + "quic bpf sockmap inherited with " + "fd:%d address:%*s", + grp->map_fd, p - v, v); + v = p + 1; + break; + + default: + break; + } + } + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_bpf_code.c b/src/event/quic/ngx_event_quic_bpf_code.c new file mode 100644 index 0000000..5c9dea1 --- /dev/null +++ b/src/event/quic/ngx_event_quic_bpf_code.c @@ -0,0 +1,88 @@ +/* AUTO-GENERATED, DO NOT EDIT. */ + +#include <stddef.h> +#include <stdint.h> + +#include "ngx_bpf.h" + + +static ngx_bpf_reloc_t bpf_reloc_prog_ngx_quic_reuseport_helper[] = { + { "ngx_quic_sockmap", 55 }, +}; + +static struct bpf_insn bpf_insn_prog_ngx_quic_reuseport_helper[] = { + /* opcode dst src offset imm */ + { 0x79, BPF_REG_4, BPF_REG_1, (int16_t) 0, 0x0 }, + { 0x79, BPF_REG_3, BPF_REG_1, (int16_t) 8, 0x0 }, + { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x8 }, + { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 54, 0x0 }, + { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x9 }, + { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 51, 0x0 }, + { 0xb7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x14 }, + { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x9 }, + { 0x71, BPF_REG_6, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0x67, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0xc7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0x65, BPF_REG_6, BPF_REG_0, (int16_t) 10, 0xffffffff }, + { 0xbf, BPF_REG_2, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0xd }, + { 0x2d, BPF_REG_2, BPF_REG_3, (int16_t) 42, 0x0 }, + { 0xbf, BPF_REG_5, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0xe }, + { 0x2d, BPF_REG_5, BPF_REG_3, (int16_t) 39, 0x0 }, + { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0xe }, + { 0x71, BPF_REG_5, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0xb7, BPF_REG_6, BPF_REG_0, (int16_t) 0, 0x8 }, + { 0x2d, BPF_REG_6, BPF_REG_5, (int16_t) 35, 0x0 }, + { 0xf, BPF_REG_5, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0xf, BPF_REG_4, BPF_REG_5, (int16_t) 0, 0x0 }, + { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 32, 0x0 }, + { 0xbf, BPF_REG_4, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x9 }, + { 0x2d, BPF_REG_4, BPF_REG_3, (int16_t) 29, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 1, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x38 }, + { 0x71, BPF_REG_3, BPF_REG_2, (int16_t) 2, 0x0 }, + { 0x67, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0x30 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 3, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x28 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 4, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x20 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 5, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x18 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 6, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x10 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_4, BPF_REG_2, (int16_t) 7, 0x0 }, + { 0x67, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x8 }, + { 0x4f, BPF_REG_3, BPF_REG_4, (int16_t) 0, 0x0 }, + { 0x71, BPF_REG_2, BPF_REG_2, (int16_t) 8, 0x0 }, + { 0x4f, BPF_REG_3, BPF_REG_2, (int16_t) 0, 0x0 }, + { 0x7b, BPF_REG_10, BPF_REG_3, (int16_t) 65528, 0x0 }, + { 0xbf, BPF_REG_3, BPF_REG_10, (int16_t) 0, 0x0 }, + { 0x7, BPF_REG_3, BPF_REG_0, (int16_t) 0, 0xfffffff8 }, + { 0x18, BPF_REG_2, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0x0, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0xb7, BPF_REG_4, BPF_REG_0, (int16_t) 0, 0x0 }, + { 0x85, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x52 }, + { 0xb7, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x1 }, + { 0x95, BPF_REG_0, BPF_REG_0, (int16_t) 0, 0x0 }, +}; + + +ngx_bpf_program_t ngx_quic_reuseport_helper = { + .relocs = bpf_reloc_prog_ngx_quic_reuseport_helper, + .nrelocs = sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper) + / sizeof(bpf_reloc_prog_ngx_quic_reuseport_helper[0]), + .ins = bpf_insn_prog_ngx_quic_reuseport_helper, + .nins = sizeof(bpf_insn_prog_ngx_quic_reuseport_helper) + / sizeof(bpf_insn_prog_ngx_quic_reuseport_helper[0]), + .license = "BSD", + .type = BPF_PROG_TYPE_SK_REUSEPORT, +}; diff --git a/src/event/quic/ngx_event_quic_connection.h b/src/event/quic/ngx_event_quic_connection.h new file mode 100644 index 0000000..824c92b --- /dev/null +++ b/src/event/quic/ngx_event_quic_connection.h @@ -0,0 +1,305 @@ +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> + + +/* #define NGX_QUIC_DEBUG_PACKETS */ /* dump packet contents */ +/* #define NGX_QUIC_DEBUG_FRAMES */ /* dump frames contents */ +/* #define NGX_QUIC_DEBUG_ALLOC */ /* log frames and bufs alloc */ +/* #define NGX_QUIC_DEBUG_CRYPTO */ + +typedef struct ngx_quic_connection_s ngx_quic_connection_t; +typedef struct ngx_quic_server_id_s ngx_quic_server_id_t; +typedef struct ngx_quic_client_id_s ngx_quic_client_id_t; +typedef struct ngx_quic_send_ctx_s ngx_quic_send_ctx_t; +typedef struct ngx_quic_socket_s ngx_quic_socket_t; +typedef struct ngx_quic_path_s ngx_quic_path_t; +typedef struct ngx_quic_keys_s ngx_quic_keys_t; + +#if (NGX_QUIC_OPENSSL_COMPAT) +#include <ngx_event_quic_openssl_compat.h> +#endif +#include <ngx_event_quic_transport.h> +#include <ngx_event_quic_protection.h> +#include <ngx_event_quic_frames.h> +#include <ngx_event_quic_migration.h> +#include <ngx_event_quic_connid.h> +#include <ngx_event_quic_streams.h> +#include <ngx_event_quic_ssl.h> +#include <ngx_event_quic_tokens.h> +#include <ngx_event_quic_ack.h> +#include <ngx_event_quic_output.h> +#include <ngx_event_quic_socket.h> + + +/* RFC 9002, 6.2.2. Handshakes and New Paths: kInitialRtt */ +#define NGX_QUIC_INITIAL_RTT 333 /* ms */ + +#define NGX_QUIC_UNSET_PN (uint64_t) -1 + +#define NGX_QUIC_SEND_CTX_LAST (NGX_QUIC_ENCRYPTION_LAST - 1) + +/* 0-RTT and 1-RTT data exist in the same packet number space, + * so we have 3 packet number spaces: + * + * 0 - Initial + * 1 - Handshake + * 2 - 0-RTT and 1-RTT + */ +#define ngx_quic_get_send_ctx(qc, level) \ + ((level) == ssl_encryption_initial) ? &((qc)->send_ctx[0]) \ + : (((level) == ssl_encryption_handshake) ? &((qc)->send_ctx[1]) \ + : &((qc)->send_ctx[2])) + +#define ngx_quic_get_connection(c) \ + (((c)->udp) ? (((ngx_quic_socket_t *)((c)->udp))->quic) : NULL) + +#define ngx_quic_get_socket(c) ((ngx_quic_socket_t *)((c)->udp)) + +#define ngx_quic_init_rtt(qc) \ + (qc)->avg_rtt = NGX_QUIC_INITIAL_RTT; \ + (qc)->rttvar = NGX_QUIC_INITIAL_RTT / 2; \ + (qc)->min_rtt = NGX_TIMER_INFINITE; \ + (qc)->first_rtt = NGX_TIMER_INFINITE; \ + (qc)->latest_rtt = 0; + + +typedef enum { + NGX_QUIC_PATH_IDLE = 0, + NGX_QUIC_PATH_VALIDATING, + NGX_QUIC_PATH_WAITING, + NGX_QUIC_PATH_MTUD +} ngx_quic_path_state_e; + + +struct ngx_quic_client_id_s { + ngx_queue_t queue; + uint64_t seqnum; + size_t len; + u_char id[NGX_QUIC_CID_LEN_MAX]; + u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; + ngx_uint_t used; /* unsigned used:1; */ +}; + + +struct ngx_quic_server_id_s { + uint64_t seqnum; + size_t len; + u_char id[NGX_QUIC_CID_LEN_MAX]; +}; + + +struct ngx_quic_path_s { + ngx_queue_t queue; + struct sockaddr *sockaddr; + ngx_sockaddr_t sa; + socklen_t socklen; + ngx_quic_client_id_t *cid; + ngx_quic_path_state_e state; + ngx_msec_t expires; + ngx_uint_t tries; + ngx_uint_t tag; + size_t mtu; + size_t mtud; + size_t max_mtu; + off_t sent; + off_t received; + u_char challenge[2][8]; + uint64_t seqnum; + uint64_t mtu_pnum[NGX_QUIC_PATH_RETRIES]; + ngx_str_t addr_text; + u_char text[NGX_SOCKADDR_STRLEN]; + unsigned validated:1; + unsigned mtu_unvalidated:1; +}; + + +struct ngx_quic_socket_s { + ngx_udp_connection_t udp; + ngx_quic_connection_t *quic; + ngx_queue_t queue; + ngx_quic_server_id_t sid; + ngx_sockaddr_t sockaddr; + socklen_t socklen; + ngx_uint_t used; /* unsigned used:1; */ +}; + + +typedef struct { + ngx_rbtree_t tree; + ngx_rbtree_node_t sentinel; + + ngx_queue_t uninitialized; + ngx_queue_t free; + + uint64_t sent; + uint64_t recv_offset; + uint64_t recv_window; + uint64_t recv_last; + uint64_t recv_max_data; + uint64_t send_offset; + uint64_t send_max_data; + + uint64_t server_max_streams_uni; + uint64_t server_max_streams_bidi; + uint64_t server_streams_uni; + uint64_t server_streams_bidi; + + uint64_t client_max_streams_uni; + uint64_t client_max_streams_bidi; + uint64_t client_streams_uni; + uint64_t client_streams_bidi; + + ngx_uint_t initialized; + /* unsigned initialized:1; */ +} ngx_quic_streams_t; + + +typedef struct { + size_t in_flight; + size_t window; + size_t ssthresh; + ngx_msec_t recovery_start; +} ngx_quic_congestion_t; + + +/* + * RFC 9000, 12.3. Packet Numbers + * + * Conceptually, a packet number space is the context in which a packet + * can be processed and acknowledged. Initial packets can only be sent + * with Initial packet protection keys and acknowledged in packets that + * are also Initial packets. + */ +struct ngx_quic_send_ctx_s { + enum ssl_encryption_level_t level; + + ngx_quic_buffer_t crypto; + uint64_t crypto_sent; + + uint64_t pnum; /* to be sent */ + uint64_t largest_ack; /* received from peer */ + uint64_t largest_pn; /* received from peer */ + + ngx_queue_t frames; /* generated frames */ + ngx_queue_t sending; /* frames assigned to pkt */ + ngx_queue_t sent; /* frames waiting ACK */ + + uint64_t pending_ack; /* non sent ack-eliciting */ + uint64_t largest_range; + uint64_t first_range; + ngx_msec_t largest_received; + ngx_msec_t ack_delay_start; + ngx_uint_t nranges; + ngx_quic_ack_range_t ranges[NGX_QUIC_MAX_RANGES]; + ngx_uint_t send_ack; +}; + + +struct ngx_quic_connection_s { + uint32_t version; + + ngx_quic_path_t *path; + + ngx_queue_t sockets; + ngx_queue_t paths; + ngx_queue_t client_ids; + ngx_queue_t free_sockets; + ngx_queue_t free_paths; + ngx_queue_t free_client_ids; + + ngx_uint_t nsockets; + ngx_uint_t nclient_ids; + uint64_t max_retired_seqnum; + uint64_t client_seqnum; + uint64_t server_seqnum; + uint64_t path_seqnum; + + ngx_quic_tp_t tp; + ngx_quic_tp_t ctp; + + ngx_quic_send_ctx_t send_ctx[NGX_QUIC_SEND_CTX_LAST]; + + ngx_quic_keys_t *keys; + + ngx_quic_conf_t *conf; + + ngx_event_t push; + ngx_event_t pto; + ngx_event_t close; + ngx_event_t path_validation; + ngx_event_t key_update; + + ngx_msec_t last_cc; + + ngx_msec_t first_rtt; + ngx_msec_t latest_rtt; + ngx_msec_t avg_rtt; + ngx_msec_t min_rtt; + ngx_msec_t rttvar; + + ngx_uint_t pto_count; + + ngx_queue_t free_frames; + ngx_buf_t *free_bufs; + ngx_buf_t *free_shadow_bufs; + + ngx_uint_t nframes; +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_uint_t nbufs; + ngx_uint_t nshadowbufs; +#endif + +#if (NGX_QUIC_OPENSSL_COMPAT) + ngx_quic_compat_t *compat; +#endif + + ngx_quic_streams_t streams; + ngx_quic_congestion_t congestion; + + uint64_t rst_pnum; /* first on validated path */ + + off_t received; + + ngx_uint_t error; + enum ssl_encryption_level_t error_level; + ngx_uint_t error_ftype; + const char *error_reason; + + ngx_uint_t shutdown_code; + const char *shutdown_reason; + + unsigned error_app:1; + unsigned send_timer_set:1; + unsigned closing:1; + unsigned shutdown:1; + unsigned draining:1; + unsigned key_phase:1; + unsigned validated:1; + unsigned client_tp_done:1; +}; + + +ngx_int_t ngx_quic_apply_transport_params(ngx_connection_t *c, + ngx_quic_tp_t *ctp); +void ngx_quic_discard_ctx(ngx_connection_t *c, + enum ssl_encryption_level_t level); +void ngx_quic_close_connection(ngx_connection_t *c, ngx_int_t rc); +void ngx_quic_shutdown_quic(ngx_connection_t *c); + +#if (NGX_DEBUG) +void ngx_quic_connstate_dbg(ngx_connection_t *c); +#else +#define ngx_quic_connstate_dbg(c) +#endif + +#endif /* _NGX_EVENT_QUIC_CONNECTION_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_connid.c b/src/event/quic/ngx_event_quic_connid.c new file mode 100644 index 0000000..f508682 --- /dev/null +++ b/src/event/quic/ngx_event_quic_connid.c @@ -0,0 +1,502 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + +#define NGX_QUIC_MAX_SERVER_IDS 8 + + +#if (NGX_QUIC_BPF) +static ngx_int_t ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id); +#endif +static ngx_int_t ngx_quic_retire_client_id(ngx_connection_t *c, + ngx_quic_client_id_t *cid); +static ngx_quic_client_id_t *ngx_quic_alloc_client_id(ngx_connection_t *c, + ngx_quic_connection_t *qc); +static ngx_int_t ngx_quic_send_server_id(ngx_connection_t *c, + ngx_quic_server_id_t *sid); + + +ngx_int_t +ngx_quic_create_server_id(ngx_connection_t *c, u_char *id) +{ + if (RAND_bytes(id, NGX_QUIC_SERVER_CID_LEN) != 1) { + return NGX_ERROR; + } + +#if (NGX_QUIC_BPF) + if (ngx_quic_bpf_attach_id(c, id) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "quic bpf failed to generate socket key"); + /* ignore error, things still may work */ + } +#endif + + return NGX_OK; +} + + +#if (NGX_QUIC_BPF) + +static ngx_int_t +ngx_quic_bpf_attach_id(ngx_connection_t *c, u_char *id) +{ + int fd; + uint64_t cookie; + socklen_t optlen; + + fd = c->listening->fd; + + optlen = sizeof(cookie); + + if (getsockopt(fd, SOL_SOCKET, SO_COOKIE, &cookie, &optlen) == -1) { + ngx_log_error(NGX_LOG_ERR, c->log, ngx_socket_errno, + "quic getsockopt(SO_COOKIE) failed"); + + return NGX_ERROR; + } + + ngx_quic_dcid_encode_key(id, cookie); + + return NGX_OK; +} + +#endif + + +ngx_int_t +ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, + ngx_quic_new_conn_id_frame_t *f) +{ + ngx_str_t id; + ngx_queue_t *q; + ngx_quic_frame_t *frame; + ngx_quic_client_id_t *cid, *item; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (f->seqnum < qc->max_retired_seqnum) { + /* + * RFC 9000, 19.15. NEW_CONNECTION_ID Frame + * + * An endpoint that receives a NEW_CONNECTION_ID frame with + * a sequence number smaller than the Retire Prior To field + * of a previously received NEW_CONNECTION_ID frame MUST send + * a corresponding RETIRE_CONNECTION_ID frame that retires + * the newly received connection ID, unless it has already + * done so for that sequence number. + */ + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; + frame->u.retire_cid.sequence_number = f->seqnum; + + ngx_quic_queue_frame(qc, frame); + + goto retire; + } + + cid = NULL; + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + item = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (item->seqnum == f->seqnum) { + cid = item; + break; + } + } + + if (cid) { + /* + * Transmission errors, timeouts, and retransmissions might cause the + * same NEW_CONNECTION_ID frame to be received multiple times. + */ + + if (cid->len != f->len + || ngx_strncmp(cid->id, f->cid, f->len) != 0 + || ngx_strncmp(cid->sr_token, f->srt, NGX_QUIC_SR_TOKEN_LEN) != 0) + { + /* + * ..if a sequence number is used for different connection IDs, + * the endpoint MAY treat that receipt as a connection error + * of type PROTOCOL_VIOLATION. + */ + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "seqnum refers to different connection id/token"; + return NGX_ERROR; + } + + } else { + + id.data = f->cid; + id.len = f->len; + + if (ngx_quic_create_client_id(c, &id, f->seqnum, f->srt) == NULL) { + return NGX_ERROR; + } + } + +retire: + + if (qc->max_retired_seqnum && f->retire <= qc->max_retired_seqnum) { + /* + * Once a sender indicates a Retire Prior To value, smaller values sent + * in subsequent NEW_CONNECTION_ID frames have no effect. A receiver + * MUST ignore any Retire Prior To fields that do not increase the + * largest received Retire Prior To value. + */ + goto done; + } + + qc->max_retired_seqnum = f->retire; + + q = ngx_queue_head(&qc->client_ids); + + while (q != ngx_queue_sentinel(&qc->client_ids)) { + + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + q = ngx_queue_next(q); + + if (cid->seqnum >= f->retire) { + continue; + } + + if (ngx_quic_retire_client_id(c, cid) != NGX_OK) { + return NGX_ERROR; + } + } + +done: + + if (qc->nclient_ids > qc->tp.active_connection_id_limit) { + /* + * RFC 9000, 5.1.1. Issuing Connection IDs + * + * After processing a NEW_CONNECTION_ID frame and + * adding and retiring active connection IDs, if the number of active + * connection IDs exceeds the value advertised in its + * active_connection_id_limit transport parameter, an endpoint MUST + * close the connection with an error of type CONNECTION_ID_LIMIT_ERROR. + */ + qc->error = NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR; + qc->error_reason = "too many connection ids received"; + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_retire_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) +{ + ngx_queue_t *q; + ngx_quic_path_t *path; + ngx_quic_client_id_t *new_cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!cid->used) { + return ngx_quic_free_client_id(c, cid); + } + + /* we are going to retire client id which is in use */ + + q = ngx_queue_head(&qc->paths); + + while (q != ngx_queue_sentinel(&qc->paths)) { + + path = ngx_queue_data(q, ngx_quic_path_t, queue); + q = ngx_queue_next(q); + + if (path->cid != cid) { + continue; + } + + if (path == qc->path) { + /* this is the active path: update it with new CID */ + new_cid = ngx_quic_next_client_id(c); + if (new_cid == NULL) { + return NGX_ERROR; + } + + qc->path->cid = new_cid; + new_cid->used = 1; + + return ngx_quic_free_client_id(c, cid); + } + + return ngx_quic_free_path(c, path); + } + + return NGX_OK; +} + + +static ngx_quic_client_id_t * +ngx_quic_alloc_client_id(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + + if (!ngx_queue_empty(&qc->free_client_ids)) { + + q = ngx_queue_head(&qc->free_client_ids); + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + ngx_queue_remove(&cid->queue); + + ngx_memzero(cid, sizeof(ngx_quic_client_id_t)); + + } else { + + cid = ngx_pcalloc(c->pool, sizeof(ngx_quic_client_id_t)); + if (cid == NULL) { + return NULL; + } + } + + return cid; +} + + +ngx_quic_client_id_t * +ngx_quic_create_client_id(ngx_connection_t *c, ngx_str_t *id, + uint64_t seqnum, u_char *token) +{ + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + cid = ngx_quic_alloc_client_id(c, qc); + if (cid == NULL) { + return NULL; + } + + cid->seqnum = seqnum; + + cid->len = id->len; + ngx_memcpy(cid->id, id->data, id->len); + + if (token) { + ngx_memcpy(cid->sr_token, token, NGX_QUIC_SR_TOKEN_LEN); + } + + ngx_queue_insert_tail(&qc->client_ids, &cid->queue); + qc->nclient_ids++; + + if (seqnum > qc->client_seqnum) { + qc->client_seqnum = seqnum; + } + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic cid seq:%uL received id:%uz:%xV:%*xs", + cid->seqnum, id->len, id, + (size_t) NGX_QUIC_SR_TOKEN_LEN, cid->sr_token); + + return cid; +} + + +ngx_quic_client_id_t * +ngx_quic_next_client_id(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->client_ids); + q != ngx_queue_sentinel(&qc->client_ids); + q = ngx_queue_next(q)) + { + cid = ngx_queue_data(q, ngx_quic_client_id_t, queue); + + if (!cid->used) { + return cid; + } + } + + return NULL; +} + + +ngx_int_t +ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_retire_cid_frame_t *f) +{ + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (f->sequence_number >= qc->server_seqnum) { + /* + * RFC 9000, 19.16. + * + * Receipt of a RETIRE_CONNECTION_ID frame containing a sequence + * number greater than any previously sent to the peer MUST be + * treated as a connection error of type PROTOCOL_VIOLATION. + */ + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "sequence number of id to retire was never issued"; + + return NGX_ERROR; + } + + qsock = ngx_quic_get_socket(c); + + if (qsock->sid.seqnum == f->sequence_number) { + + /* + * RFC 9000, 19.16. + * + * The sequence number specified in a RETIRE_CONNECTION_ID frame MUST + * NOT refer to the Destination Connection ID field of the packet in + * which the frame is contained. The peer MAY treat this as a + * connection error of type PROTOCOL_VIOLATION. + */ + + qc->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + qc->error_reason = "sequence number of id to retire refers DCID"; + + return NGX_ERROR; + } + + qsock = ngx_quic_find_socket(c, f->sequence_number); + if (qsock == NULL) { + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket seq:%uL is retired", qsock->sid.seqnum); + + ngx_quic_close_socket(c, qsock); + + /* restore socket count up to a limit after deletion */ + if (ngx_quic_create_sockets(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_create_sockets(ngx_connection_t *c) +{ + ngx_uint_t n; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + n = ngx_min(NGX_QUIC_MAX_SERVER_IDS, qc->ctp.active_connection_id_limit); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic create sockets has:%ui max:%ui", qc->nsockets, n); + + while (qc->nsockets < n) { + + qsock = ngx_quic_create_socket(c, qc); + if (qsock == NULL) { + return NGX_ERROR; + } + + if (ngx_quic_listen(c, qc, qsock) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_send_server_id(c, &qsock->sid) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_server_id(ngx_connection_t *c, ngx_quic_server_id_t *sid) +{ + ngx_str_t dcid; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + dcid.len = sid->len; + dcid.data = sid->id; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_CONNECTION_ID; + frame->u.ncid.seqnum = sid->seqnum; + frame->u.ncid.retire = 0; + frame->u.ncid.len = NGX_QUIC_SERVER_CID_LEN; + ngx_memcpy(frame->u.ncid.cid, sid->id, NGX_QUIC_SERVER_CID_LEN); + + if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, + frame->u.ncid.srt) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_free_client_id(ngx_connection_t *c, ngx_quic_client_id_t *cid) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RETIRE_CONNECTION_ID; + frame->u.retire_cid.sequence_number = cid->seqnum; + + ngx_quic_queue_frame(qc, frame); + + /* we are no longer going to use this client id */ + + ngx_queue_remove(&cid->queue); + ngx_queue_insert_head(&qc->free_client_ids, &cid->queue); + + qc->nclient_ids--; + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_connid.h b/src/event/quic/ngx_event_quic_connid.h new file mode 100644 index 0000000..33e9c65 --- /dev/null +++ b/src/event/quic/ngx_event_quic_connid.h @@ -0,0 +1,29 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ +#define _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +ngx_int_t ngx_quic_handle_retire_connection_id_frame(ngx_connection_t *c, + ngx_quic_retire_cid_frame_t *f); +ngx_int_t ngx_quic_handle_new_connection_id_frame(ngx_connection_t *c, + ngx_quic_new_conn_id_frame_t *f); + +ngx_int_t ngx_quic_create_sockets(ngx_connection_t *c); +ngx_int_t ngx_quic_create_server_id(ngx_connection_t *c, u_char *id); + +ngx_quic_client_id_t *ngx_quic_create_client_id(ngx_connection_t *c, + ngx_str_t *id, uint64_t seqnum, u_char *token); +ngx_quic_client_id_t *ngx_quic_next_client_id(ngx_connection_t *c); +ngx_int_t ngx_quic_free_client_id(ngx_connection_t *c, + ngx_quic_client_id_t *cid); + +#endif /* _NGX_EVENT_QUIC_CONNID_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_frames.c b/src/event/quic/ngx_event_quic_frames.c new file mode 100644 index 0000000..6ea908c --- /dev/null +++ b/src/event/quic/ngx_event_quic_frames.c @@ -0,0 +1,895 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + + +#define NGX_QUIC_BUFFER_SIZE 4096 + +#define ngx_quic_buf_refs(b) (b)->shadow->num +#define ngx_quic_buf_inc_refs(b) ngx_quic_buf_refs(b)++ +#define ngx_quic_buf_dec_refs(b) ngx_quic_buf_refs(b)-- +#define ngx_quic_buf_set_refs(b, v) ngx_quic_buf_refs(b) = v + + +static ngx_buf_t *ngx_quic_alloc_buf(ngx_connection_t *c); +static void ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b); +static ngx_buf_t *ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b); +static ngx_int_t ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, + off_t offset); + + +static ngx_buf_t * +ngx_quic_alloc_buf(ngx_connection_t *c) +{ + u_char *p; + ngx_buf_t *b; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + b = qc->free_bufs; + + if (b) { + qc->free_bufs = b->shadow; + p = b->start; + + } else { + b = qc->free_shadow_bufs; + + if (b) { + qc->free_shadow_bufs = b->shadow; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic use shadow buffer n:%ui %ui", + ++qc->nbufs, --qc->nshadowbufs); +#endif + + } else { + b = ngx_palloc(c->pool, sizeof(ngx_buf_t)); + if (b == NULL) { + return NULL; + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new buffer n:%ui", ++qc->nbufs); +#endif + } + + p = ngx_pnalloc(c->pool, NGX_QUIC_BUFFER_SIZE); + if (p == NULL) { + return NULL; + } + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic alloc buffer %p", b); +#endif + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->tag = (ngx_buf_tag_t) &ngx_quic_alloc_buf; + b->temporary = 1; + b->shadow = b; + + b->start = p; + b->pos = p; + b->last = p; + b->end = p + NGX_QUIC_BUFFER_SIZE; + + ngx_quic_buf_set_refs(b, 1); + + return b; +} + + +static void +ngx_quic_free_buf(ngx_connection_t *c, ngx_buf_t *b) +{ + ngx_buf_t *shadow; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_quic_buf_dec_refs(b); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic free buffer %p r:%ui", + b, (ngx_uint_t) ngx_quic_buf_refs(b)); +#endif + + shadow = b->shadow; + + if (ngx_quic_buf_refs(b) == 0) { + shadow->shadow = qc->free_bufs; + qc->free_bufs = shadow; + } + + if (b != shadow) { + b->shadow = qc->free_shadow_bufs; + qc->free_shadow_bufs = b; + } + +} + + +static ngx_buf_t * +ngx_quic_clone_buf(ngx_connection_t *c, ngx_buf_t *b) +{ + ngx_buf_t *nb; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + nb = qc->free_shadow_bufs; + + if (nb) { + qc->free_shadow_bufs = nb->shadow; + + } else { + nb = ngx_palloc(c->pool, sizeof(ngx_buf_t)); + if (nb == NULL) { + return NULL; + } + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic new shadow buffer n:%ui", ++qc->nshadowbufs); +#endif + } + + *nb = *b; + + ngx_quic_buf_inc_refs(b); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic clone buffer %p %p r:%ui", + b, nb, (ngx_uint_t) ngx_quic_buf_refs(b)); +#endif + + return nb; +} + + +static ngx_int_t +ngx_quic_split_chain(ngx_connection_t *c, ngx_chain_t *cl, off_t offset) +{ + ngx_buf_t *b, *tb; + ngx_chain_t *tail; + + b = cl->buf; + + tail = ngx_alloc_chain_link(c->pool); + if (tail == NULL) { + return NGX_ERROR; + } + + tb = ngx_quic_clone_buf(c, b); + if (tb == NULL) { + return NGX_ERROR; + } + + tail->buf = tb; + + tb->pos += offset; + + b->last = tb->pos; + b->last_buf = 0; + + tail->next = cl->next; + cl->next = tail; + + return NGX_OK; +} + + +ngx_quic_frame_t * +ngx_quic_alloc_frame(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_queue_empty(&qc->free_frames)) { + + q = ngx_queue_head(&qc->free_frames); + frame = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(&frame->queue); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic reuse frame n:%ui", qc->nframes); +#endif + + } else if (qc->nframes < 10000) { + frame = ngx_palloc(c->pool, sizeof(ngx_quic_frame_t)); + if (frame == NULL) { + return NULL; + } + + ++qc->nframes; + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic alloc frame n:%ui", qc->nframes); +#endif + + } else { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic flood detected"); + return NULL; + } + + ngx_memzero(frame, sizeof(ngx_quic_frame_t)); + + return frame; +} + + +void +ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (frame->data) { + ngx_quic_free_chain(c, frame->data); + } + + ngx_queue_insert_head(&qc->free_frames, &frame->queue); + +#ifdef NGX_QUIC_DEBUG_ALLOC + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic free frame n:%ui", qc->nframes); +#endif +} + + +void +ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in) +{ + ngx_chain_t *cl; + + while (in) { + cl = in; + in = in->next; + + ngx_quic_free_buf(c, cl->buf); + ngx_free_chain(c->pool, cl); + } +} + + +void +ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + + do { + q = ngx_queue_head(frames); + + if (q == ngx_queue_sentinel(frames)) { + break; + } + + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_quic_free_frame(c, f); + } while (1); +} + + +void +ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame) +{ + ngx_quic_send_ctx_t *ctx; + + ctx = ngx_quic_get_send_ctx(qc, frame->level); + + ngx_queue_insert_tail(&ctx->frames, &frame->queue); + + frame->len = ngx_quic_create_frame(NULL, frame); + /* always succeeds */ + + if (qc->closing) { + return; + } + + ngx_post_event(&qc->push, &ngx_posted_events); +} + + +ngx_int_t +ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, size_t len) +{ + size_t shrink; + ngx_chain_t *out; + ngx_quic_frame_t *nf; + ngx_quic_buffer_t qb; + ngx_quic_ordered_frame_t *of, *onf; + + switch (f->type) { + case NGX_QUIC_FT_CRYPTO: + case NGX_QUIC_FT_STREAM: + break; + + default: + return NGX_DECLINED; + } + + if ((size_t) f->len <= len) { + return NGX_OK; + } + + shrink = f->len - len; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic split frame now:%uz need:%uz shrink:%uz", + f->len, len, shrink); + + of = &f->u.ord; + + if (of->length <= shrink) { + return NGX_DECLINED; + } + + of->length -= shrink; + f->len = ngx_quic_create_frame(NULL, f); + + if ((size_t) f->len > len) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "could not split QUIC frame"); + return NGX_ERROR; + } + + ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); + qb.chain = f->data; + + out = ngx_quic_read_buffer(c, &qb, of->length); + if (out == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + f->data = out; + + nf = ngx_quic_alloc_frame(c); + if (nf == NULL) { + return NGX_ERROR; + } + + *nf = *f; + onf = &nf->u.ord; + onf->offset += of->length; + onf->length = shrink; + nf->len = ngx_quic_create_frame(NULL, nf); + nf->data = qb.chain; + + if (f->type == NGX_QUIC_FT_STREAM) { + f->u.stream.fin = 0; + } + + ngx_queue_insert_after(&f->queue, &nf->queue); + + return NGX_OK; +} + + +ngx_chain_t * +ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data, size_t len) +{ + ngx_buf_t buf; + ngx_chain_t cl, *out; + ngx_quic_buffer_t qb; + + ngx_memzero(&buf, sizeof(ngx_buf_t)); + + buf.pos = data; + buf.last = buf.pos + len; + buf.temporary = 1; + + cl.buf = &buf; + cl.next = NULL; + + ngx_memzero(&qb, sizeof(ngx_quic_buffer_t)); + + if (ngx_quic_write_buffer(c, &qb, &cl, len, 0) == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + out = ngx_quic_read_buffer(c, &qb, len); + if (out == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + ngx_quic_free_buffer(c, &qb); + + return out; +} + + +ngx_chain_t * +ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, uint64_t limit) +{ + uint64_t n; + ngx_buf_t *b; + ngx_chain_t *out, **ll; + + out = qb->chain; + + for (ll = &out; *ll; ll = &(*ll)->next) { + b = (*ll)->buf; + + if (b->sync) { + /* hole */ + break; + } + + if (limit == 0) { + break; + } + + n = b->last - b->pos; + + if (n > limit) { + if (ngx_quic_split_chain(c, *ll, limit) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + n = limit; + } + + limit -= n; + qb->offset += n; + } + + if (qb->offset >= qb->last_offset) { + qb->last_chain = NULL; + } + + qb->chain = *ll; + *ll = NULL; + + return out; +} + + +void +ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + uint64_t offset) +{ + size_t n; + ngx_buf_t *b; + ngx_chain_t *cl; + + while (qb->chain) { + if (qb->offset >= offset) { + break; + } + + cl = qb->chain; + b = cl->buf; + n = b->last - b->pos; + + if (qb->offset + n > offset) { + n = offset - qb->offset; + b->pos += n; + qb->offset += n; + break; + } + + qb->offset += n; + qb->chain = cl->next; + + cl->next = NULL; + ngx_quic_free_chain(c, cl); + } + + if (qb->chain == NULL) { + qb->offset = offset; + } + + if (qb->offset >= qb->last_offset) { + qb->last_chain = NULL; + } +} + + +ngx_chain_t * +ngx_quic_alloc_chain(ngx_connection_t *c) +{ + ngx_chain_t *cl; + + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = ngx_quic_alloc_buf(c); + if (cl->buf == NULL) { + return NULL; + } + + return cl; +} + + +ngx_chain_t * +ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + ngx_chain_t *in, uint64_t limit, uint64_t offset) +{ + u_char *p; + uint64_t n, base; + ngx_buf_t *b; + ngx_chain_t *cl, **chain; + + if (qb->last_chain && offset >= qb->last_offset) { + base = qb->last_offset; + chain = &qb->last_chain; + + } else { + base = qb->offset; + chain = &qb->chain; + } + + while (in && limit) { + + if (offset < base) { + n = ngx_min((uint64_t) (in->buf->last - in->buf->pos), + ngx_min(base - offset, limit)); + + in->buf->pos += n; + offset += n; + limit -= n; + + if (in->buf->pos == in->buf->last) { + in = in->next; + } + + continue; + } + + cl = *chain; + + if (cl == NULL) { + cl = ngx_quic_alloc_chain(c); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf->last = cl->buf->end; + cl->buf->sync = 1; /* hole */ + cl->next = NULL; + *chain = cl; + } + + b = cl->buf; + n = b->last - b->pos; + + if (base + n <= offset) { + base += n; + chain = &cl->next; + continue; + } + + if (b->sync && offset > base) { + if (ngx_quic_split_chain(c, cl, offset - base) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + continue; + } + + p = b->pos + (offset - base); + + while (in) { + + if (!ngx_buf_in_memory(in->buf) || in->buf->pos == in->buf->last) { + in = in->next; + continue; + } + + if (p == b->last || limit == 0) { + break; + } + + n = ngx_min(b->last - p, in->buf->last - in->buf->pos); + n = ngx_min(n, limit); + + if (b->sync) { + ngx_memcpy(p, in->buf->pos, n); + qb->size += n; + } + + p += n; + in->buf->pos += n; + offset += n; + limit -= n; + } + + if (b->sync && p == b->last) { + b->sync = 0; + continue; + } + + if (b->sync && p != b->pos) { + if (ngx_quic_split_chain(c, cl, p - b->pos) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + b->sync = 0; + } + } + + qb->last_offset = base; + qb->last_chain = *chain; + + return in; +} + + +void +ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb) +{ + ngx_quic_free_chain(c, qb->chain); + + qb->chain = NULL; + qb->last_chain = NULL; +} + + +#if (NGX_DEBUG) + +void +ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx) +{ + u_char *p, *last, *pos, *end; + ssize_t n; + uint64_t gap, range, largest, smallest; + ngx_uint_t i; + u_char buf[NGX_MAX_ERROR_STR]; + + p = buf; + last = buf + sizeof(buf); + + switch (f->type) { + + case NGX_QUIC_FT_CRYPTO: + p = ngx_slprintf(p, last, "CRYPTO len:%uL off:%uL", + f->u.crypto.length, f->u.crypto.offset); + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " data:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + + break; + + case NGX_QUIC_FT_PADDING: + p = ngx_slprintf(p, last, "PADDING"); + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + p = ngx_slprintf(p, last, "ACK n:%ui delay:%uL ", + f->u.ack.range_count, f->u.ack.delay); + + if (f->data) { + pos = f->data->buf->pos; + end = f->data->buf->last; + + } else { + pos = NULL; + end = NULL; + } + + largest = f->u.ack.largest; + smallest = f->u.ack.largest - f->u.ack.first_range; + + if (largest == smallest) { + p = ngx_slprintf(p, last, "%uL", largest); + + } else { + p = ngx_slprintf(p, last, "%uL-%uL", largest, smallest); + } + + for (i = 0; i < f->u.ack.range_count; i++) { + n = ngx_quic_parse_ack_range(log, pos, end, &gap, &range); + if (n == NGX_ERROR) { + break; + } + + pos += n; + + largest = smallest - gap - 2; + smallest = largest - range; + + if (largest == smallest) { + p = ngx_slprintf(p, last, " %uL", largest); + + } else { + p = ngx_slprintf(p, last, " %uL-%uL", largest, smallest); + } + } + + if (f->type == NGX_QUIC_FT_ACK_ECN) { + p = ngx_slprintf(p, last, " ECN counters ect0:%uL ect1:%uL ce:%uL", + f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); + } + break; + + case NGX_QUIC_FT_PING: + p = ngx_slprintf(p, last, "PING"); + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + p = ngx_slprintf(p, last, + "NEW_CONNECTION_ID seq:%uL retire:%uL len:%ud", + f->u.ncid.seqnum, f->u.ncid.retire, f->u.ncid.len); + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + p = ngx_slprintf(p, last, "RETIRE_CONNECTION_ID seqnum:%uL", + f->u.retire_cid.sequence_number); + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + p = ngx_slprintf(p, last, "CONNECTION_CLOSE%s err:%ui", + f->type == NGX_QUIC_FT_CONNECTION_CLOSE ? "" : "_APP", + f->u.close.error_code); + + if (f->u.close.reason.len) { + p = ngx_slprintf(p, last, " %V", &f->u.close.reason); + } + + if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { + p = ngx_slprintf(p, last, " ft:%ui", f->u.close.frame_type); + } + + break; + + case NGX_QUIC_FT_STREAM: + p = ngx_slprintf(p, last, "STREAM id:0x%xL", f->u.stream.stream_id); + + if (f->u.stream.off) { + p = ngx_slprintf(p, last, " off:%uL", f->u.stream.offset); + } + + if (f->u.stream.len) { + p = ngx_slprintf(p, last, " len:%uL", f->u.stream.length); + } + + if (f->u.stream.fin) { + p = ngx_slprintf(p, last, " fin:1"); + } + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " data:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + + break; + + case NGX_QUIC_FT_MAX_DATA: + p = ngx_slprintf(p, last, "MAX_DATA max_data:%uL on recv", + f->u.max_data.max_data); + break; + + case NGX_QUIC_FT_RESET_STREAM: + p = ngx_slprintf(p, last, "RESET_STREAM" + " id:0x%xL error_code:0x%xL final_size:0x%xL", + f->u.reset_stream.id, f->u.reset_stream.error_code, + f->u.reset_stream.final_size); + break; + + case NGX_QUIC_FT_STOP_SENDING: + p = ngx_slprintf(p, last, "STOP_SENDING id:0x%xL err:0x%xL", + f->u.stop_sending.id, f->u.stop_sending.error_code); + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + p = ngx_slprintf(p, last, "STREAMS_BLOCKED limit:%uL bidi:%ui", + f->u.streams_blocked.limit, f->u.streams_blocked.bidi); + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + p = ngx_slprintf(p, last, "MAX_STREAMS limit:%uL bidi:%ui", + f->u.max_streams.limit, f->u.max_streams.bidi); + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + p = ngx_slprintf(p, last, "MAX_STREAM_DATA id:0x%xL limit:%uL", + f->u.max_stream_data.id, f->u.max_stream_data.limit); + break; + + + case NGX_QUIC_FT_DATA_BLOCKED: + p = ngx_slprintf(p, last, "DATA_BLOCKED limit:%uL", + f->u.data_blocked.limit); + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + p = ngx_slprintf(p, last, "STREAM_DATA_BLOCKED id:0x%xL limit:%uL", + f->u.stream_data_blocked.id, + f->u.stream_data_blocked.limit); + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + p = ngx_slprintf(p, last, "PATH_CHALLENGE data:0x%*xs", + sizeof(f->u.path_challenge.data), + f->u.path_challenge.data); + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + p = ngx_slprintf(p, last, "PATH_RESPONSE data:0x%*xs", + sizeof(f->u.path_challenge.data), + f->u.path_challenge.data); + break; + + case NGX_QUIC_FT_NEW_TOKEN: + p = ngx_slprintf(p, last, "NEW_TOKEN"); + +#ifdef NGX_QUIC_DEBUG_FRAMES + { + ngx_chain_t *cl; + + p = ngx_slprintf(p, last, " token:"); + + for (cl = f->data; cl; cl = cl->next) { + p = ngx_slprintf(p, last, "%*xs", + cl->buf->last - cl->buf->pos, cl->buf->pos); + } + } +#endif + + break; + + case NGX_QUIC_FT_HANDSHAKE_DONE: + p = ngx_slprintf(p, last, "HANDSHAKE DONE"); + break; + + default: + p = ngx_slprintf(p, last, "unknown type 0x%xi", f->type); + break; + } + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, log, 0, "quic frame %s %s:%uL %*s", + tx ? "tx" : "rx", ngx_quic_level_name(f->level), f->pnum, + p - buf, buf); +} + +#endif diff --git a/src/event/quic/ngx_event_quic_frames.h b/src/event/quic/ngx_event_quic_frames.h new file mode 100644 index 0000000..2d55af9 --- /dev/null +++ b/src/event/quic/ngx_event_quic_frames.h @@ -0,0 +1,45 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ +#define _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +typedef ngx_int_t (*ngx_quic_frame_handler_pt)(ngx_connection_t *c, + ngx_quic_frame_t *frame, void *data); + + +ngx_quic_frame_t *ngx_quic_alloc_frame(ngx_connection_t *c); +void ngx_quic_free_frame(ngx_connection_t *c, ngx_quic_frame_t *frame); +void ngx_quic_free_frames(ngx_connection_t *c, ngx_queue_t *frames); +void ngx_quic_queue_frame(ngx_quic_connection_t *qc, ngx_quic_frame_t *frame); +ngx_int_t ngx_quic_split_frame(ngx_connection_t *c, ngx_quic_frame_t *f, + size_t len); + +ngx_chain_t *ngx_quic_alloc_chain(ngx_connection_t *c); +void ngx_quic_free_chain(ngx_connection_t *c, ngx_chain_t *in); + +ngx_chain_t *ngx_quic_copy_buffer(ngx_connection_t *c, u_char *data, + size_t len); +ngx_chain_t *ngx_quic_read_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + uint64_t limit); +ngx_chain_t *ngx_quic_write_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + ngx_chain_t *in, uint64_t limit, uint64_t offset); +void ngx_quic_skip_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb, + uint64_t offset); +void ngx_quic_free_buffer(ngx_connection_t *c, ngx_quic_buffer_t *qb); + +#if (NGX_DEBUG) +void ngx_quic_log_frame(ngx_log_t *log, ngx_quic_frame_t *f, ngx_uint_t tx); +#else +#define ngx_quic_log_frame(log, f, tx) +#endif + +#endif /* _NGX_EVENT_QUIC_FRAMES_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_migration.c b/src/event/quic/ngx_event_quic_migration.c new file mode 100644 index 0000000..2d1467e --- /dev/null +++ b/src/event/quic/ngx_event_quic_migration.c @@ -0,0 +1,1003 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + + +#define NGX_QUIC_PATH_MTU_DELAY 100 +#define NGX_QUIC_PATH_MTU_PRECISION 16 + + +static void ngx_quic_set_connection_path(ngx_connection_t *c, + ngx_quic_path_t *path); +static ngx_int_t ngx_quic_validate_path(ngx_connection_t *c, + ngx_quic_path_t *path); +static ngx_int_t ngx_quic_send_path_challenge(ngx_connection_t *c, + ngx_quic_path_t *path); +static void ngx_quic_set_path_timer(ngx_connection_t *c); +static ngx_int_t ngx_quic_expire_path_validation(ngx_connection_t *c, + ngx_quic_path_t *path); +static ngx_int_t ngx_quic_expire_path_mtu_delay(ngx_connection_t *c, + ngx_quic_path_t *path); +static ngx_int_t ngx_quic_expire_path_mtu_discovery(ngx_connection_t *c, + ngx_quic_path_t *path); +static ngx_quic_path_t *ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag); +static ngx_int_t ngx_quic_send_path_mtu_probe(ngx_connection_t *c, + ngx_quic_path_t *path); + + +ngx_int_t +ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f) +{ + size_t min; + ngx_quic_frame_t *fp; + ngx_quic_connection_t *qc; + + if (pkt->level != ssl_encryption_application || pkt->path_challenged) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ignoring PATH_CHALLENGE"); + return NGX_OK; + } + + pkt->path_challenged = 1; + + qc = ngx_quic_get_connection(c); + + fp = ngx_quic_alloc_frame(c); + if (fp == NULL) { + return NGX_ERROR; + } + + fp->level = ssl_encryption_application; + fp->type = NGX_QUIC_FT_PATH_RESPONSE; + fp->u.path_response = *f; + + /* + * RFC 9000, 8.2.2. Path Validation Responses + * + * A PATH_RESPONSE frame MUST be sent on the network path where the + * PATH_CHALLENGE frame was received. + */ + + /* + * An endpoint MUST expand datagrams that contain a PATH_RESPONSE frame + * to at least the smallest allowed maximum datagram size of 1200 bytes. + * ... + * However, an endpoint MUST NOT expand the datagram containing the + * PATH_RESPONSE if the resulting data exceeds the anti-amplification limit. + */ + + min = (ngx_quic_path_limit(c, pkt->path, 1200) < 1200) ? 0 : 1200; + + if (ngx_quic_frame_sendto(c, fp, min, pkt->path) == NGX_ERROR) { + return NGX_ERROR; + } + + if (pkt->path == qc->path) { + /* + * RFC 9000, 9.3.3. Off-Path Packet Forwarding + * + * An endpoint that receives a PATH_CHALLENGE on an active path SHOULD + * send a non-probing packet in response. + */ + + fp = ngx_quic_alloc_frame(c); + if (fp == NULL) { + return NGX_ERROR; + } + + fp->level = ssl_encryption_application; + fp->type = NGX_QUIC_FT_PING; + + ngx_quic_queue_frame(qc, fp); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_path_response_frame(ngx_connection_t *c, + ngx_quic_path_challenge_frame_t *f) +{ + ngx_uint_t rst; + ngx_queue_t *q; + ngx_quic_path_t *path, *prev; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + /* + * RFC 9000, 8.2.3. Successful Path Validation + * + * A PATH_RESPONSE frame received on any network path validates the path + * on which the PATH_CHALLENGE was sent. + */ + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (path->state != NGX_QUIC_PATH_VALIDATING) { + continue; + } + + if (ngx_memcmp(path->challenge[0], f->data, sizeof(f->data)) == 0 + || ngx_memcmp(path->challenge[1], f->data, sizeof(f->data)) == 0) + { + goto valid; + } + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stale PATH_RESPONSE ignored"); + + return NGX_OK; + +valid: + + /* + * RFC 9000, 9.4. Loss Detection and Congestion Control + * + * On confirming a peer's ownership of its new address, + * an endpoint MUST immediately reset the congestion controller + * and round-trip time estimator for the new path to initial values + * unless the only change in the peer's address is its port number. + */ + + rst = 1; + + prev = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); + + if (prev != NULL) { + + if (ngx_cmp_sockaddr(prev->sockaddr, prev->socklen, + path->sockaddr, path->socklen, 0) + == NGX_OK) + { + /* address did not change */ + rst = 0; + + path->mtu = prev->mtu; + path->max_mtu = prev->max_mtu; + path->mtu_unvalidated = 0; + } + } + + if (rst) { + /* prevent old path packets contribution to congestion control */ + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + qc->rst_pnum = ctx->pnum; + + ngx_memzero(&qc->congestion, sizeof(ngx_quic_congestion_t)); + + qc->congestion.window = ngx_min(10 * qc->tp.max_udp_payload_size, + ngx_max(2 * qc->tp.max_udp_payload_size, + 14720)); + qc->congestion.ssthresh = (size_t) -1; + qc->congestion.recovery_start = ngx_current_msec; + + ngx_quic_init_rtt(qc); + } + + path->validated = 1; + + if (path->mtu_unvalidated) { + path->mtu_unvalidated = 0; + return ngx_quic_validate_path(c, path); + } + + /* + * RFC 9000, 9.3. Responding to Connection Migration + * + * After verifying a new client address, the server SHOULD + * send new address validation tokens (Section 8) to the client. + */ + + if (ngx_quic_send_new_token(c, path) != NGX_OK) { + return NGX_ERROR; + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic path seq:%uL addr:%V successfully validated", + path->seqnum, &path->addr_text); + + ngx_quic_path_dbg(c, "is validated", path); + + ngx_quic_discover_path_mtu(c, path); + + return NGX_OK; +} + + +ngx_quic_path_t * +ngx_quic_new_path(ngx_connection_t *c, + struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid) +{ + ngx_queue_t *q; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!ngx_queue_empty(&qc->free_paths)) { + + q = ngx_queue_head(&qc->free_paths); + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + ngx_queue_remove(&path->queue); + + ngx_memzero(path, sizeof(ngx_quic_path_t)); + + } else { + + path = ngx_pcalloc(c->pool, sizeof(ngx_quic_path_t)); + if (path == NULL) { + return NULL; + } + } + + ngx_queue_insert_tail(&qc->paths, &path->queue); + + path->cid = cid; + cid->used = 1; + + path->seqnum = qc->path_seqnum++; + + path->sockaddr = &path->sa.sockaddr; + path->socklen = socklen; + ngx_memcpy(path->sockaddr, sockaddr, socklen); + + path->addr_text.data = path->text; + path->addr_text.len = ngx_sock_ntop(sockaddr, socklen, path->text, + NGX_SOCKADDR_STRLEN, 1); + + path->mtu = NGX_QUIC_MIN_INITIAL_SIZE; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL created addr:%V", + path->seqnum, &path->addr_text); + return path; +} + + +static ngx_quic_path_t * +ngx_quic_get_path(ngx_connection_t *c, ngx_uint_t tag) +{ + ngx_queue_t *q; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (path->tag == tag) { + return path; + } + } + + return NULL; +} + + +ngx_int_t +ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + off_t len; + ngx_queue_t *q; + ngx_quic_path_t *path, *probe; + ngx_quic_socket_t *qsock; + ngx_quic_send_ctx_t *ctx; + ngx_quic_client_id_t *cid; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + qsock = ngx_quic_get_socket(c); + + len = pkt->raw->last - pkt->raw->start; + + if (c->udp->buffer == NULL) { + /* first ever packet in connection, path already exists */ + path = qc->path; + goto update; + } + + probe = NULL; + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (ngx_cmp_sockaddr(&qsock->sockaddr.sockaddr, qsock->socklen, + path->sockaddr, path->socklen, 1) + == NGX_OK) + { + goto update; + } + + if (path->tag == NGX_QUIC_PATH_PROBE) { + probe = path; + } + } + + /* packet from new path, drop current probe, if any */ + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + /* + * only accept highest-numbered packets to prevent connection id + * exhaustion by excessive probing packets from unknown paths + */ + if (pkt->pn != ctx->largest_pn) { + return NGX_DONE; + } + + if (probe && ngx_quic_free_path(c, probe) != NGX_OK) { + return NGX_ERROR; + } + + /* new path requires new client id */ + cid = ngx_quic_next_client_id(c); + if (cid == NULL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic no available client ids for new path"); + /* stop processing of this datagram */ + return NGX_DONE; + } + + path = ngx_quic_new_path(c, &qsock->sockaddr.sockaddr, qsock->socklen, cid); + if (path == NULL) { + return NGX_ERROR; + } + + path->tag = NGX_QUIC_PATH_PROBE; + + /* + * client arrived using new path and previously seen DCID, + * this indicates NAT rebinding (or bad client) + */ + if (qsock->used) { + pkt->rebound = 1; + } + +update: + + qsock->used = 1; + pkt->path = path; + + /* TODO: this may be too late in some cases; + * for example, if error happens during decrypt(), we cannot + * send CC, if error happens in 1st packet, due to amplification + * limit, because path->received = 0 + * + * should we account garbage as received or only decrypting packets? + */ + path->received += len; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet len:%O via sock seq:%L path seq:%uL", + len, (int64_t) qsock->sid.seqnum, path->seqnum); + ngx_quic_path_dbg(c, "status", path); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_queue_remove(&path->queue); + ngx_queue_insert_head(&qc->free_paths, &path->queue); + + /* + * invalidate CID that is no longer usable for any other path; + * this also requests new CIDs from client + */ + if (path->cid) { + if (ngx_quic_free_client_id(c, path->cid) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL addr:%V retired", + path->seqnum, &path->addr_text); + + return NGX_OK; +} + + +static void +ngx_quic_set_connection_path(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_memcpy(c->sockaddr, path->sockaddr, path->socklen); + c->socklen = path->socklen; + + if (c->addr_text.data) { + c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen, + c->addr_text.data, + c->listening->addr_text_max_len, 0); + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send path set to seq:%uL addr:%V", + path->seqnum, &path->addr_text); +} + + +ngx_int_t +ngx_quic_handle_migration(ngx_connection_t *c, ngx_quic_header_t *pkt) +{ + ngx_quic_path_t *next, *bkp; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + /* got non-probing packet via non-active path */ + + qc = ngx_quic_get_connection(c); + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + + /* + * RFC 9000, 9.3. Responding to Connection Migration + * + * An endpoint only changes the address to which it sends packets in + * response to the highest-numbered non-probing packet. + */ + if (pkt->pn != ctx->largest_pn) { + return NGX_OK; + } + + next = pkt->path; + + /* + * RFC 9000, 9.3.3: + * + * In response to an apparent migration, endpoints MUST validate the + * previously active path using a PATH_CHALLENGE frame. + */ + if (pkt->rebound) { + + /* NAT rebinding: client uses new path with old SID */ + if (ngx_quic_validate_path(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } + } + + if (qc->path->validated) { + + if (next->tag != NGX_QUIC_PATH_BACKUP) { + /* can delete backup path, if any */ + bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); + + if (bkp && ngx_quic_free_path(c, bkp) != NGX_OK) { + return NGX_ERROR; + } + } + + qc->path->tag = NGX_QUIC_PATH_BACKUP; + ngx_quic_path_dbg(c, "is now backup", qc->path); + + } else { + if (ngx_quic_free_path(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } + } + + /* switch active path to migrated */ + qc->path = next; + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + + ngx_quic_set_connection_path(c, next); + + if (!next->validated && next->state != NGX_QUIC_PATH_VALIDATING) { + if (ngx_quic_validate_path(c, next) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic migrated to path seq:%uL addr:%V", + qc->path->seqnum, &qc->path->addr_text); + + ngx_quic_path_dbg(c, "is now active", qc->path); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_validate_path(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_msec_t pto; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic initiated validation of path seq:%uL", path->seqnum); + + path->tries = 0; + + if (RAND_bytes((u_char *) path->challenge, sizeof(path->challenge)) != 1) { + return NGX_ERROR; + } + + (void) ngx_quic_send_path_challenge(c, path); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + pto = ngx_max(ngx_quic_pto(c, ctx), 1000); + + path->expires = ngx_current_msec + pto; + path->state = NGX_QUIC_PATH_VALIDATING; + + ngx_quic_set_path_timer(c); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_path_challenge(ngx_connection_t *c, ngx_quic_path_t *path) +{ + size_t min; + ngx_uint_t n; + ngx_quic_frame_t *frame; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL send path_challenge tries:%ui", + path->seqnum, path->tries); + + for (n = 0; n < 2; n++) { + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_PATH_CHALLENGE; + + ngx_memcpy(frame->u.path_challenge.data, path->challenge[n], 8); + + /* + * RFC 9000, 8.2.1. Initiating Path Validation + * + * An endpoint MUST expand datagrams that contain a PATH_CHALLENGE frame + * to at least the smallest allowed maximum datagram size of 1200 bytes, + * unless the anti-amplification limit for the path does not permit + * sending a datagram of this size. + */ + + if (path->mtu_unvalidated + || ngx_quic_path_limit(c, path, 1200) < 1200) + { + min = 0; + path->mtu_unvalidated = 1; + + } else { + min = 1200; + } + + if (ngx_quic_frame_sendto(c, frame, min, path) == NGX_ERROR) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +void +ngx_quic_discover_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (path->max_mtu) { + if (path->max_mtu - path->mtu <= NGX_QUIC_PATH_MTU_PRECISION) { + path->state = NGX_QUIC_PATH_IDLE; + ngx_quic_set_path_timer(c); + return; + } + + path->mtud = (path->mtu + path->max_mtu) / 2; + + } else { + path->mtud = path->mtu * 2; + + if (path->mtud >= qc->ctp.max_udp_payload_size) { + path->mtud = qc->ctp.max_udp_payload_size; + path->max_mtu = qc->ctp.max_udp_payload_size; + } + } + + path->state = NGX_QUIC_PATH_WAITING; + path->expires = ngx_current_msec + NGX_QUIC_PATH_MTU_DELAY; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL schedule mtu:%uz", + path->seqnum, path->mtud); + + ngx_quic_set_path_timer(c); +} + + +static void +ngx_quic_set_path_timer(ngx_connection_t *c) +{ + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t left, next; + ngx_quic_path_t *path; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + now = ngx_current_msec; + next = -1; + + for (q = ngx_queue_head(&qc->paths); + q != ngx_queue_sentinel(&qc->paths); + q = ngx_queue_next(q)) + { + path = ngx_queue_data(q, ngx_quic_path_t, queue); + + if (path->state == NGX_QUIC_PATH_IDLE) { + continue; + } + + left = path->expires - now; + left = ngx_max(left, 1); + + if (next == -1 || left < next) { + next = left; + } + } + + if (next != -1) { + ngx_add_timer(&qc->path_validation, next); + + } else if (qc->path_validation.timer_set) { + ngx_del_timer(&qc->path_validation); + } +} + + +void +ngx_quic_path_handler(ngx_event_t *ev) +{ + ngx_msec_t now; + ngx_queue_t *q; + ngx_msec_int_t left; + ngx_quic_path_t *path; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ev->data; + qc = ngx_quic_get_connection(c); + + now = ngx_current_msec; + + q = ngx_queue_head(&qc->paths); + + while (q != ngx_queue_sentinel(&qc->paths)) { + + path = ngx_queue_data(q, ngx_quic_path_t, queue); + q = ngx_queue_next(q); + + if (path->state == NGX_QUIC_PATH_IDLE) { + continue; + } + + left = path->expires - now; + + if (left > 0) { + continue; + } + + switch (path->state) { + case NGX_QUIC_PATH_VALIDATING: + if (ngx_quic_expire_path_validation(c, path) != NGX_OK) { + goto failed; + } + + break; + + case NGX_QUIC_PATH_WAITING: + if (ngx_quic_expire_path_mtu_delay(c, path) != NGX_OK) { + goto failed; + } + + break; + + case NGX_QUIC_PATH_MTUD: + if (ngx_quic_expire_path_mtu_discovery(c, path) != NGX_OK) { + goto failed; + } + + break; + + default: + break; + } + } + + ngx_quic_set_path_timer(c); + + return; + +failed: + + ngx_quic_close_connection(c, NGX_ERROR); +} + + +static ngx_int_t +ngx_quic_expire_path_validation(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_msec_int_t pto; + ngx_quic_path_t *bkp; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + if (++path->tries < NGX_QUIC_PATH_RETRIES) { + pto = ngx_max(ngx_quic_pto(c, ctx), 1000) << path->tries; + path->expires = ngx_current_msec + pto; + + (void) ngx_quic_send_path_challenge(c, path); + + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL validation failed", path->seqnum); + + /* found expired path */ + + path->validated = 0; + + + /* RFC 9000, 9.3.2. On-Path Address Spoofing + * + * To protect the connection from failing due to such a spurious + * migration, an endpoint MUST revert to using the last validated + * peer address when validation of a new peer address fails. + */ + + if (qc->path == path) { + /* active path validation failed */ + + bkp = ngx_quic_get_path(c, NGX_QUIC_PATH_BACKUP); + + if (bkp == NULL) { + qc->error = NGX_QUIC_ERR_NO_VIABLE_PATH; + qc->error_reason = "no viable path"; + return NGX_ERROR; + } + + qc->path = bkp; + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + + ngx_quic_set_connection_path(c, qc->path); + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic path seq:%uL addr:%V is restored from backup", + qc->path->seqnum, &qc->path->addr_text); + + ngx_quic_path_dbg(c, "is active", qc->path); + } + + return ngx_quic_free_path(c, path); +} + + +static ngx_int_t +ngx_quic_expire_path_mtu_delay(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_int_t rc; + ngx_uint_t i; + ngx_msec_t pto; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + path->tries = 0; + + for ( ;; ) { + + for (i = 0; i < NGX_QUIC_PATH_RETRIES; i++) { + path->mtu_pnum[i] = NGX_QUIC_UNSET_PN; + } + + rc = ngx_quic_send_path_mtu_probe(c, path); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + pto = ngx_quic_pto(c, ctx); + path->expires = ngx_current_msec + pto; + path->state = NGX_QUIC_PATH_MTUD; + return NGX_OK; + } + + /* rc == NGX_DECLINED */ + + path->max_mtu = path->mtud; + + if (path->max_mtu - path->mtu <= NGX_QUIC_PATH_MTU_PRECISION) { + path->state = NGX_QUIC_PATH_IDLE; + return NGX_OK; + } + + path->mtud = (path->mtu + path->max_mtu) / 2; + } +} + + +static ngx_int_t +ngx_quic_expire_path_mtu_discovery(ngx_connection_t *c, ngx_quic_path_t *path) +{ + ngx_int_t rc; + ngx_msec_int_t pto; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + if (++path->tries < NGX_QUIC_PATH_RETRIES) { + rc = ngx_quic_send_path_mtu_probe(c, path); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_OK) { + pto = ngx_quic_pto(c, ctx) << path->tries; + path->expires = ngx_current_msec + pto; + return NGX_OK; + } + + /* rc == NGX_DECLINED */ + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL expired mtu:%uz", + path->seqnum, path->mtud); + + path->max_mtu = path->mtud; + + ngx_quic_discover_path_mtu(c, path); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_send_path_mtu_probe(ngx_connection_t *c, ngx_quic_path_t *path) +{ + size_t mtu; + uint64_t pnum; + ngx_int_t rc; + ngx_uint_t log_error; + ngx_quic_frame_t *frame; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_PING; + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + pnum = ctx->pnum; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL send probe " + "mtu:%uz pnum:%uL tries:%ui", + path->seqnum, path->mtud, ctx->pnum, path->tries); + + log_error = c->log_error; + c->log_error = NGX_ERROR_IGNORE_EMSGSIZE; + + mtu = path->mtu; + path->mtu = path->mtud; + + rc = ngx_quic_frame_sendto(c, frame, path->mtud, path); + + path->mtu = mtu; + c->log_error = log_error; + + if (rc == NGX_OK) { + path->mtu_pnum[path->tries] = pnum; + return NGX_OK; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL rejected mtu:%uz", + path->seqnum, path->mtud); + + if (rc == NGX_ERROR) { + if (c->write->error) { + c->write->error = 0; + return NGX_DECLINED; + } + + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path, + uint64_t min, uint64_t max) +{ + uint64_t pnum; + ngx_uint_t i; + + if (path->state != NGX_QUIC_PATH_MTUD) { + return NGX_OK; + } + + for (i = 0; i < NGX_QUIC_PATH_RETRIES; i++) { + pnum = path->mtu_pnum[i]; + + if (pnum == NGX_QUIC_UNSET_PN) { + continue; + } + + if (pnum < min || pnum > max) { + continue; + } + + path->mtu = path->mtud; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic path seq:%uL ack mtu:%uz", + path->seqnum, path->mtu); + + ngx_quic_discover_path_mtu(c, path); + + break; + } + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_migration.h b/src/event/quic/ngx_event_quic_migration.h new file mode 100644 index 0000000..270c572 --- /dev/null +++ b/src/event/quic/ngx_event_quic_migration.h @@ -0,0 +1,45 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + +#define NGX_QUIC_PATH_RETRIES 3 + +#define NGX_QUIC_PATH_PROBE 0 +#define NGX_QUIC_PATH_ACTIVE 1 +#define NGX_QUIC_PATH_BACKUP 2 + +#define ngx_quic_path_dbg(c, msg, path) \ + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, c->log, 0, \ + "quic path seq:%uL %s tx:%O rx:%O valid:%d st:%d mtu:%uz", \ + path->seqnum, msg, path->sent, path->received, \ + path->validated, path->state, path->mtu); + +ngx_int_t ngx_quic_handle_path_challenge_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_path_challenge_frame_t *f); +ngx_int_t ngx_quic_handle_path_response_frame(ngx_connection_t *c, + ngx_quic_path_challenge_frame_t *f); + +ngx_quic_path_t *ngx_quic_new_path(ngx_connection_t *c, + struct sockaddr *sockaddr, socklen_t socklen, ngx_quic_client_id_t *cid); +ngx_int_t ngx_quic_free_path(ngx_connection_t *c, ngx_quic_path_t *path); + +ngx_int_t ngx_quic_set_path(ngx_connection_t *c, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_handle_migration(ngx_connection_t *c, + ngx_quic_header_t *pkt); + +void ngx_quic_path_handler(ngx_event_t *ev); + +void ngx_quic_discover_path_mtu(ngx_connection_t *c, ngx_quic_path_t *path); +ngx_int_t ngx_quic_handle_path_mtu(ngx_connection_t *c, + ngx_quic_path_t *path, uint64_t min, uint64_t max); + +#endif /* _NGX_EVENT_QUIC_MIGRATION_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_openssl_compat.c b/src/event/quic/ngx_event_quic_openssl_compat.c new file mode 100644 index 0000000..6052bc6 --- /dev/null +++ b/src/event/quic/ngx_event_quic_openssl_compat.c @@ -0,0 +1,652 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + + +#if (NGX_QUIC_OPENSSL_COMPAT) + +#define NGX_QUIC_COMPAT_RECORD_SIZE 1024 + +#define NGX_QUIC_COMPAT_SSL_TP_EXT 0x39 + +#define NGX_QUIC_COMPAT_CLIENT_HANDSHAKE "CLIENT_HANDSHAKE_TRAFFIC_SECRET" +#define NGX_QUIC_COMPAT_SERVER_HANDSHAKE "SERVER_HANDSHAKE_TRAFFIC_SECRET" +#define NGX_QUIC_COMPAT_CLIENT_APPLICATION "CLIENT_TRAFFIC_SECRET_0" +#define NGX_QUIC_COMPAT_SERVER_APPLICATION "SERVER_TRAFFIC_SECRET_0" + + +typedef struct { + ngx_quic_secret_t secret; + ngx_uint_t cipher; +} ngx_quic_compat_keys_t; + + +typedef struct { + ngx_log_t *log; + + u_char type; + ngx_str_t payload; + uint64_t number; + ngx_quic_compat_keys_t *keys; + + enum ssl_encryption_level_t level; +} ngx_quic_compat_record_t; + + +struct ngx_quic_compat_s { + const SSL_QUIC_METHOD *method; + + enum ssl_encryption_level_t write_level; + + uint64_t read_record; + ngx_quic_compat_keys_t keys; + + ngx_str_t tp; + ngx_str_t ctp; +}; + + +static void ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line); +static ngx_int_t ngx_quic_compat_set_encryption_secret(ngx_connection_t *c, + ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len); +static void ngx_quic_compat_cleanup_encryption_secret(void *data); +static int ngx_quic_compat_add_transport_params_callback(SSL *ssl, + unsigned int ext_type, unsigned int context, const unsigned char **out, + size_t *outlen, X509 *x, size_t chainidx, int *al, void *add_arg); +static int ngx_quic_compat_parse_transport_params_callback(SSL *ssl, + unsigned int ext_type, unsigned int context, const unsigned char *in, + size_t inlen, X509 *x, size_t chainidx, int *al, void *parse_arg); +static void ngx_quic_compat_message_callback(int write_p, int version, + int content_type, const void *buf, size_t len, SSL *ssl, void *arg); +static size_t ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, + u_char *out, ngx_uint_t plain); +static ngx_int_t ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, + ngx_str_t *res); + + +ngx_int_t +ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx) +{ + SSL_CTX_set_keylog_callback(ctx, ngx_quic_compat_keylog_callback); + + if (SSL_CTX_has_client_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT)) { + return NGX_OK; + } + + if (SSL_CTX_add_custom_ext(ctx, NGX_QUIC_COMPAT_SSL_TP_EXT, + SSL_EXT_CLIENT_HELLO + |SSL_EXT_TLS1_3_ENCRYPTED_EXTENSIONS, + ngx_quic_compat_add_transport_params_callback, + NULL, + NULL, + ngx_quic_compat_parse_transport_params_callback, + NULL) + == 0) + { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "SSL_CTX_add_custom_ext() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_quic_compat_keylog_callback(const SSL *ssl, const char *line) +{ + u_char ch, *p, *start, value; + size_t n; + ngx_uint_t write; + const SSL_CIPHER *cipher; + ngx_quic_compat_t *com; + ngx_connection_t *c; + ngx_quic_connection_t *qc; + enum ssl_encryption_level_t level; + u_char secret[EVP_MAX_MD_SIZE]; + + c = ngx_ssl_get_connection(ssl); + if (c->type != SOCK_DGRAM) { + return; + } + + p = (u_char *) line; + + for (start = p; *p && *p != ' '; p++); + + n = p - start; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat secret %*s", n, start); + + if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_HANDSHAKE) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_HANDSHAKE, n) == 0) + { + level = ssl_encryption_handshake; + write = 0; + + } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_HANDSHAKE) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_HANDSHAKE, n) == 0) + { + level = ssl_encryption_handshake; + write = 1; + + } else if (n == sizeof(NGX_QUIC_COMPAT_CLIENT_APPLICATION) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_CLIENT_APPLICATION, n) + == 0) + { + level = ssl_encryption_application; + write = 0; + + } else if (n == sizeof(NGX_QUIC_COMPAT_SERVER_APPLICATION) - 1 + && ngx_strncmp(start, NGX_QUIC_COMPAT_SERVER_APPLICATION, n) + == 0) + { + level = ssl_encryption_application; + write = 1; + + } else { + return; + } + + if (*p++ == '\0') { + return; + } + + for ( /* void */ ; *p && *p != ' '; p++); + + if (*p++ == '\0') { + return; + } + + for (n = 0, start = p; *p; p++) { + ch = *p; + + if (ch >= '0' && ch <= '9') { + value = ch - '0'; + goto next; + } + + ch = (u_char) (ch | 0x20); + + if (ch >= 'a' && ch <= 'f') { + value = ch - 'a' + 10; + goto next; + } + + ngx_log_error(NGX_LOG_EMERG, c->log, 0, + "invalid OpenSSL QUIC secret format"); + + return; + + next: + + if ((p - start) % 2) { + secret[n++] += value; + + } else { + if (n >= EVP_MAX_MD_SIZE) { + ngx_log_error(NGX_LOG_EMERG, c->log, 0, + "too big OpenSSL QUIC secret"); + return; + } + + secret[n] = (value << 4); + } + } + + qc = ngx_quic_get_connection(c); + com = qc->compat; + cipher = SSL_get_current_cipher(ssl); + + if (write) { + com->method->set_write_secret((SSL *) ssl, level, cipher, secret, n); + com->write_level = level; + + } else { + com->method->set_read_secret((SSL *) ssl, level, cipher, secret, n); + com->read_record = 0; + + (void) ngx_quic_compat_set_encryption_secret(c, &com->keys, level, + cipher, secret, n); + } + + ngx_explicit_memzero(secret, n); +} + + +static ngx_int_t +ngx_quic_compat_set_encryption_secret(ngx_connection_t *c, + ngx_quic_compat_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) +{ + ngx_int_t key_len; + ngx_str_t secret_str; + ngx_uint_t i; + ngx_quic_md_t key; + ngx_quic_hkdf_t seq[2]; + ngx_quic_secret_t *peer_secret; + ngx_quic_ciphers_t ciphers; + ngx_pool_cleanup_t *cln; + + peer_secret = &keys->secret; + + keys->cipher = SSL_CIPHER_get_id(cipher); + + key_len = ngx_quic_ciphers(keys->cipher, &ciphers); + + if (key_len == NGX_ERROR) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, "unexpected cipher"); + return NGX_ERROR; + } + + key.len = key_len; + + peer_secret->iv.len = NGX_QUIC_IV_LEN; + + secret_str.len = secret_len; + secret_str.data = (u_char *) secret; + + ngx_quic_hkdf_set(&seq[0], "tls13 key", &key, &secret_str); + ngx_quic_hkdf_set(&seq[1], "tls13 iv", &peer_secret->iv, &secret_str); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) { + return NGX_ERROR; + } + } + + /* register cleanup handler once */ + + if (peer_secret->ctx) { + ngx_quic_crypto_cleanup(peer_secret); + + } else { + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_quic_compat_cleanup_encryption_secret; + cln->data = peer_secret; + } + + if (ngx_quic_crypto_init(ciphers.c, peer_secret, &key, 1, c->log) + == NGX_ERROR) + { + return NGX_ERROR; + } + + ngx_explicit_memzero(key.data, key.len); + + return NGX_OK; +} + + +static void +ngx_quic_compat_cleanup_encryption_secret(void *data) +{ + ngx_quic_secret_t *secret = data; + + ngx_quic_crypto_cleanup(secret); +} + + +static int +ngx_quic_compat_add_transport_params_callback(SSL *ssl, unsigned int ext_type, + unsigned int context, const unsigned char **out, size_t *outlen, X509 *x, + size_t chainidx, int *al, void *add_arg) +{ + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + if (c->type != SOCK_DGRAM) { + return 0; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat add transport params"); + + qc = ngx_quic_get_connection(c); + com = qc->compat; + + *out = com->tp.data; + *outlen = com->tp.len; + + return 1; +} + + +static int +ngx_quic_compat_parse_transport_params_callback(SSL *ssl, unsigned int ext_type, + unsigned int context, const unsigned char *in, size_t inlen, X509 *x, + size_t chainidx, int *al, void *parse_arg) +{ + u_char *p; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + if (c->type != SOCK_DGRAM) { + return 0; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat parse transport params"); + + qc = ngx_quic_get_connection(c); + com = qc->compat; + + p = ngx_pnalloc(c->pool, inlen); + if (p == NULL) { + return 0; + } + + ngx_memcpy(p, in, inlen); + + com->ctp.data = p; + com->ctp.len = inlen; + + return 1; +} + + +int +SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method) +{ + BIO *rbio, *wbio; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat set method"); + + qc = ngx_quic_get_connection(c); + + qc->compat = ngx_pcalloc(c->pool, sizeof(ngx_quic_compat_t)); + if (qc->compat == NULL) { + return 0; + } + + com = qc->compat; + com->method = quic_method; + + rbio = BIO_new(BIO_s_mem()); + if (rbio == NULL) { + return 0; + } + + wbio = BIO_new(BIO_s_null()); + if (wbio == NULL) { + BIO_free(rbio); + return 0; + } + + SSL_set_bio(ssl, rbio, wbio); + + SSL_set_msg_callback(ssl, ngx_quic_compat_message_callback); + + /* early data is not supported */ + SSL_set_max_early_data(ssl, 0); + + return 1; +} + + +static void +ngx_quic_compat_message_callback(int write_p, int version, int content_type, + const void *buf, size_t len, SSL *ssl, void *arg) +{ + ngx_uint_t alert; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + enum ssl_encryption_level_t level; + + if (!write_p) { + return; + } + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + + if (qc == NULL) { + /* closing */ + return; + } + + com = qc->compat; + level = com->write_level; + + switch (content_type) { + + case SSL3_RT_HANDSHAKE: + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat tx %s len:%uz ", + ngx_quic_level_name(level), len); + + if (com->method->add_handshake_data(ssl, level, buf, len) != 1) { + goto failed; + } + + break; + + case SSL3_RT_ALERT: + if (len >= 2) { + alert = ((u_char *) buf)[1]; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat %s alert:%ui len:%uz ", + ngx_quic_level_name(level), alert, len); + + if (com->method->send_alert(ssl, level, alert) != 1) { + goto failed; + } + } + + break; + } + + return; + +failed: + + ngx_post_event(&qc->close, &ngx_posted_events); +} + + +int +SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len) +{ + BIO *rbio; + size_t n; + u_char *p; + ngx_str_t res; + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + ngx_quic_compat_record_t rec; + u_char in[NGX_QUIC_COMPAT_RECORD_SIZE + 1]; + u_char out[NGX_QUIC_COMPAT_RECORD_SIZE + 1 + + SSL3_RT_HEADER_LENGTH + + NGX_QUIC_TAG_LEN]; + + c = ngx_ssl_get_connection(ssl); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic compat rx %s len:%uz", + ngx_quic_level_name(level), len); + + qc = ngx_quic_get_connection(c); + com = qc->compat; + rbio = SSL_get_rbio(ssl); + + while (len) { + ngx_memzero(&rec, sizeof(ngx_quic_compat_record_t)); + + rec.type = SSL3_RT_HANDSHAKE; + rec.log = c->log; + rec.number = com->read_record++; + rec.keys = &com->keys; + rec.level = level; + + if (level == ssl_encryption_initial) { + n = ngx_min(len, 65535); + + rec.payload.len = n; + rec.payload.data = (u_char *) data; + + ngx_quic_compat_create_header(&rec, out, 1); + + BIO_write(rbio, out, SSL3_RT_HEADER_LENGTH); + BIO_write(rbio, data, n); + +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat record len:%uz %*xs%*xs", + n + SSL3_RT_HEADER_LENGTH, + (size_t) SSL3_RT_HEADER_LENGTH, out, n, data); +#endif + + } else { + n = ngx_min(len, NGX_QUIC_COMPAT_RECORD_SIZE); + + p = ngx_cpymem(in, data, n); + *p++ = SSL3_RT_HANDSHAKE; + + rec.payload.len = p - in; + rec.payload.data = in; + + res.data = out; + + if (ngx_quic_compat_create_record(&rec, &res) != NGX_OK) { + return 0; + } + +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic compat record len:%uz %xV", res.len, &res); +#endif + + BIO_write(rbio, res.data, res.len); + } + + data += n; + len -= n; + } + + return 1; +} + + +static size_t +ngx_quic_compat_create_header(ngx_quic_compat_record_t *rec, u_char *out, + ngx_uint_t plain) +{ + u_char type; + size_t len; + + len = rec->payload.len; + + if (plain) { + type = rec->type; + + } else { + type = SSL3_RT_APPLICATION_DATA; + len += NGX_QUIC_TAG_LEN; + } + + out[0] = type; + out[1] = 0x03; + out[2] = 0x03; + out[3] = (len >> 8); + out[4] = len; + + return 5; +} + + +static ngx_int_t +ngx_quic_compat_create_record(ngx_quic_compat_record_t *rec, ngx_str_t *res) +{ + ngx_str_t ad, out; + ngx_quic_secret_t *secret; + u_char nonce[NGX_QUIC_IV_LEN]; + + ad.data = res->data; + ad.len = ngx_quic_compat_create_header(rec, ad.data, 0); + + out.len = rec->payload.len + NGX_QUIC_TAG_LEN; + out.data = res->data + ad.len; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, rec->log, 0, + "quic compat ad len:%uz %xV", ad.len, &ad); +#endif + + secret = &rec->keys->secret; + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); + ngx_quic_compute_nonce(nonce, sizeof(nonce), rec->number); + + if (ngx_quic_crypto_seal(secret, &out, nonce, &rec->payload, &ad, rec->log) + != NGX_OK) + { + return NGX_ERROR; + } + + res->len = ad.len + out.len; + + return NGX_OK; +} + + +int +SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, + size_t params_len) +{ + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + com = qc->compat; + + com->tp.len = params_len; + com->tp.data = (u_char *) params; + + return 1; +} + + +void +SSL_get_peer_quic_transport_params(const SSL *ssl, const uint8_t **out_params, + size_t *out_params_len) +{ + ngx_connection_t *c; + ngx_quic_compat_t *com; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection(ssl); + qc = ngx_quic_get_connection(c); + com = qc->compat; + + *out_params = com->ctp.data; + *out_params_len = com->ctp.len; +} + +#endif /* NGX_QUIC_OPENSSL_COMPAT */ diff --git a/src/event/quic/ngx_event_quic_openssl_compat.h b/src/event/quic/ngx_event_quic_openssl_compat.h new file mode 100644 index 0000000..77cc3cb --- /dev/null +++ b/src/event/quic/ngx_event_quic_openssl_compat.h @@ -0,0 +1,59 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ +#define _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ + +#if defined SSL_R_MISSING_QUIC_TRANSPORT_PARAMETERS_EXTENSION \ + || defined LIBRESSL_VERSION_NUMBER +#undef NGX_QUIC_OPENSSL_COMPAT +#else + + +#include <ngx_config.h> +#include <ngx_core.h> + + +typedef struct ngx_quic_compat_s ngx_quic_compat_t; + + +enum ssl_encryption_level_t { + ssl_encryption_initial = 0, + ssl_encryption_early_data, + ssl_encryption_handshake, + ssl_encryption_application +}; + + +typedef struct ssl_quic_method_st { + int (*set_read_secret)(SSL *ssl, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, + const uint8_t *rsecret, size_t secret_len); + int (*set_write_secret)(SSL *ssl, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, + const uint8_t *wsecret, size_t secret_len); + int (*add_handshake_data)(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len); + int (*flush_flight)(SSL *ssl); + int (*send_alert)(SSL *ssl, enum ssl_encryption_level_t level, + uint8_t alert); +} SSL_QUIC_METHOD; + + +ngx_int_t ngx_quic_compat_init(ngx_conf_t *cf, SSL_CTX *ctx); + +int SSL_set_quic_method(SSL *ssl, const SSL_QUIC_METHOD *quic_method); +int SSL_provide_quic_data(SSL *ssl, enum ssl_encryption_level_t level, + const uint8_t *data, size_t len); +int SSL_set_quic_transport_params(SSL *ssl, const uint8_t *params, + size_t params_len); +void SSL_get_peer_quic_transport_params(const SSL *ssl, + const uint8_t **out_params, size_t *out_params_len); + + +#endif /* TLSEXT_TYPE_quic_transport_parameters */ + +#endif /* _NGX_EVENT_QUIC_OPENSSL_COMPAT_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_output.c b/src/event/quic/ngx_event_quic_output.c new file mode 100644 index 0000000..f087e2b --- /dev/null +++ b/src/event/quic/ngx_event_quic_output.c @@ -0,0 +1,1319 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + + +#define NGX_QUIC_MAX_UDP_SEGMENT_BUF 65487 /* 65K - IPv6 header */ +#define NGX_QUIC_MAX_SEGMENTS 64 /* UDP_MAX_SEGMENTS */ + +#define NGX_QUIC_RETRY_TOKEN_LIFETIME 3 /* seconds */ +#define NGX_QUIC_NEW_TOKEN_LIFETIME 600 /* seconds */ +#define NGX_QUIC_RETRY_BUFFER_SIZE 256 + /* 1 flags + 4 version + 3 x (1 + 20) s/o/dcid + itag + token(64) */ + +/* + * RFC 9000, 10.3. Stateless Reset + * + * Endpoints MUST discard packets that are too small to be valid QUIC + * packets. With the set of AEAD functions defined in [QUIC-TLS], + * short header packets that are smaller than 21 bytes are never valid. + */ +#define NGX_QUIC_MIN_PKT_LEN 21 + +#define NGX_QUIC_MIN_SR_PACKET 43 /* 5 rand + 16 srt + 22 padding */ +#define NGX_QUIC_MAX_SR_PACKET 1200 + +#define NGX_QUIC_CC_MIN_INTERVAL 1000 /* 1s */ + +#define NGX_QUIC_SOCKET_RETRY_DELAY 10 /* ms, for NGX_AGAIN on write */ + + +#define ngx_quic_log_packet(log, pkt) \ + ngx_log_debug6(NGX_LOG_DEBUG_EVENT, log, 0, \ + "quic packet tx %s bytes:%ui need_ack:%d" \ + " number:%L encoded nl:%d trunc:0x%xD", \ + ngx_quic_level_name((pkt)->level), (pkt)->payload.len, \ + (pkt)->need_ack, (pkt)->number, (pkt)->num_len, \ + (pkt)->trunc); + + +static ngx_int_t ngx_quic_create_datagrams(ngx_connection_t *c); +static void ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx); +static void ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pnum); +#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) +static ngx_uint_t ngx_quic_allow_segmentation(ngx_connection_t *c); +static ngx_int_t ngx_quic_create_segments(ngx_connection_t *c); +static ssize_t ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, + size_t len, struct sockaddr *sockaddr, socklen_t socklen, size_t segment); +#endif +static ssize_t ngx_quic_output_packet(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, u_char *data, size_t max, size_t min); +static void ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_quic_header_t *pkt, ngx_quic_path_t *path); +static ngx_uint_t ngx_quic_get_padding_level(ngx_connection_t *c); +static ssize_t ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen); +static void ngx_quic_set_packet_number(ngx_quic_header_t *pkt, + ngx_quic_send_ctx_t *ctx); + + +ngx_int_t +ngx_quic_output(ngx_connection_t *c) +{ + size_t in_flight; + ngx_int_t rc; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + c->log->action = "sending frames"; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + + in_flight = cg->in_flight; + +#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) + if (ngx_quic_allow_segmentation(c)) { + rc = ngx_quic_create_segments(c); + } else +#endif + { + rc = ngx_quic_create_datagrams(c); + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + + if (in_flight == cg->in_flight || qc->closing) { + /* no ack-eliciting data was sent or we are done */ + return NGX_OK; + } + + if (!qc->send_timer_set) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + } + + ngx_quic_set_lost_timer(c); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_datagrams(ngx_connection_t *c) +{ + size_t len, min; + ssize_t n; + u_char *p; + uint64_t preserved_pnum[NGX_QUIC_SEND_CTX_LAST]; + ngx_uint_t i, pad; + ngx_quic_path_t *path; + ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + path = qc->path; + + while (cg->in_flight < cg->window) { + + p = dst; + + len = ngx_quic_path_limit(c, path, path->mtu); + + pad = ngx_quic_get_padding_level(c); + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + + ctx = &qc->send_ctx[i]; + + preserved_pnum[i] = ctx->pnum; + + if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + min = (i == pad && p - dst < NGX_QUIC_MIN_INITIAL_SIZE) + ? NGX_QUIC_MIN_INITIAL_SIZE - (p - dst) : 0; + + if (min > len) { + /* padding can't be applied - avoid sending the packet */ + + while (i-- > 0) { + ctx = &qc->send_ctx[i]; + ngx_quic_revert_send(c, ctx, preserved_pnum[i]); + } + + return NGX_OK; + } + + n = ngx_quic_output_packet(c, ctx, p, len, min); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + p += n; + len -= n; + } + + len = p - dst; + if (len == 0) { + break; + } + + n = ngx_quic_send(c, dst, len, path->sockaddr, path->socklen); + + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (n == NGX_AGAIN) { + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_revert_send(c, &qc->send_ctx[i], preserved_pnum[i]); + } + + ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY); + break; + } + + for (i = 0; i < NGX_QUIC_SEND_CTX_LAST; i++) { + ngx_quic_commit_send(c, &qc->send_ctx[i]); + } + + path->sent += len; + } + + return NGX_OK; +} + + +static void +ngx_quic_commit_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + cg = &qc->congestion; + + while (!ngx_queue_empty(&ctx->sending)) { + + q = ngx_queue_head(&ctx->sending); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + ngx_queue_remove(q); + + if (f->pkt_need_ack && !qc->closing) { + ngx_queue_insert_tail(&ctx->sent, q); + + cg->in_flight += f->plen; + + } else { + ngx_quic_free_frame(c, f); + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion send if:%uz", cg->in_flight); +} + + +static void +ngx_quic_revert_send(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t pnum) +{ + ngx_queue_t *q; + + while (!ngx_queue_empty(&ctx->sending)) { + + q = ngx_queue_last(&ctx->sending); + ngx_queue_remove(q); + ngx_queue_insert_head(&ctx->frames, q); + } + + ctx->pnum = pnum; +} + + +#if ((NGX_HAVE_UDP_SEGMENT) && (NGX_HAVE_MSGHDR_MSG_CONTROL)) + +static ngx_uint_t +ngx_quic_allow_segmentation(ngx_connection_t *c) +{ + size_t bytes, len; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (!qc->conf->gso_enabled) { + return 0; + } + + if (!qc->path->validated) { + /* don't even try to be faster on non-validated paths */ + return 0; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + if (!ngx_queue_empty(&ctx->frames)) { + return 0; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + if (!ngx_queue_empty(&ctx->frames)) { + return 0; + } + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + bytes = 0; + len = ngx_min(qc->path->mtu, NGX_QUIC_MAX_UDP_SEGMENT_BUF); + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + bytes += f->len; + + if (bytes > len * 3) { + /* require at least ~3 full packets to batch */ + return 1; + } + } + + return 0; +} + + +static ngx_int_t +ngx_quic_create_segments(ngx_connection_t *c) +{ + size_t len, segsize; + ssize_t n; + u_char *p, *end; + uint64_t preserved_pnum; + ngx_uint_t nseg; + ngx_quic_path_t *path; + ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + static u_char dst[NGX_QUIC_MAX_UDP_SEGMENT_BUF]; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + path = qc->path; + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_application); + + if (ngx_quic_generate_ack(c, ctx) != NGX_OK) { + return NGX_ERROR; + } + + segsize = ngx_min(path->mtu, NGX_QUIC_MAX_UDP_SEGMENT_BUF); + p = dst; + end = dst + sizeof(dst); + + nseg = 0; + + preserved_pnum = ctx->pnum; + + for ( ;; ) { + + len = ngx_min(segsize, (size_t) (end - p)); + + if (len && cg->in_flight + (p - dst) < cg->window) { + + n = ngx_quic_output_packet(c, ctx, p, len, len); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (n) { + p += n; + nseg++; + } + + } else { + n = 0; + } + + if (p == dst) { + break; + } + + if (n == 0 || nseg == NGX_QUIC_MAX_SEGMENTS) { + n = ngx_quic_send_segments(c, dst, p - dst, path->sockaddr, + path->socklen, segsize); + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + if (n == NGX_AGAIN) { + ngx_quic_revert_send(c, ctx, preserved_pnum); + + ngx_add_timer(&qc->push, NGX_QUIC_SOCKET_RETRY_DELAY); + break; + } + + ngx_quic_commit_send(c, ctx); + + path->sent += n; + + p = dst; + nseg = 0; + preserved_pnum = ctx->pnum; + } + } + + return NGX_OK; +} + + +static ssize_t +ngx_quic_send_segments(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen, size_t segment) +{ + size_t clen; + ssize_t n; + uint16_t *valp; + struct iovec iov; + struct msghdr msg; + struct cmsghdr *cmsg; + +#if (NGX_HAVE_ADDRINFO_CMSG) + char msg_control[CMSG_SPACE(sizeof(uint16_t)) + + CMSG_SPACE(sizeof(ngx_addrinfo_t))]; +#else + char msg_control[CMSG_SPACE(sizeof(uint16_t))]; +#endif + + ngx_memzero(&msg, sizeof(struct msghdr)); + ngx_memzero(msg_control, sizeof(msg_control)); + + iov.iov_len = len; + iov.iov_base = (void *) buf; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_name = sockaddr; + msg.msg_namelen = socklen; + + msg.msg_control = msg_control; + msg.msg_controllen = sizeof(msg_control); + + cmsg = CMSG_FIRSTHDR(&msg); + + cmsg->cmsg_level = SOL_UDP; + cmsg->cmsg_type = UDP_SEGMENT; + cmsg->cmsg_len = CMSG_LEN(sizeof(uint16_t)); + + clen = CMSG_SPACE(sizeof(uint16_t)); + + valp = (void *) CMSG_DATA(cmsg); + *valp = segment; + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (c->listening && c->listening->wildcard && c->local_sockaddr) { + cmsg = CMSG_NXTHDR(&msg, cmsg); + clen += ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); + } +#endif + + msg.msg_controllen = clen; + + n = ngx_sendmsg(c, &msg, 0); + if (n < 0) { + return n; + } + + c->sent += n; + + return n; +} + +#endif + + + +static ngx_uint_t +ngx_quic_get_padding_level(ngx_connection_t *c) +{ + ngx_uint_t i; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + + /* + * RFC 9000, 14.1. Initial Datagram Size + * + * Similarly, a server MUST expand the payload of all UDP datagrams + * carrying ack-eliciting Initial packets to at least the smallest + * allowed maximum datagram size of 1200 bytes. + */ + + qc = ngx_quic_get_connection(c); + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_initial); + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (f->need_ack) { + for (i = 0; i + 1 < NGX_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i + 1]; + + if (ngx_queue_empty(&ctx->frames)) { + break; + } + } + + return i; + } + } + + return NGX_QUIC_SEND_CTX_LAST; +} + + +static ssize_t +ngx_quic_output_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + u_char *data, size_t max, size_t min) +{ + size_t len, pad, min_payload, max_payload; + u_char *p; + ssize_t flen; + ngx_str_t res; + ngx_int_t rc; + ngx_uint_t nframes; + ngx_msec_t now; + ngx_queue_t *q; + ngx_quic_frame_t *f; + ngx_quic_header_t pkt; + ngx_quic_connection_t *qc; + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + if (ngx_queue_empty(&ctx->frames)) { + return 0; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic output %s packet max:%uz min:%uz", + ngx_quic_level_name(ctx->level), max, min); + + qc = ngx_quic_get_connection(c); + + if (!ngx_quic_keys_available(qc->keys, ctx->level, 1)) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "quic %s write keys discarded", + ngx_quic_level_name(ctx->level)); + + while (!ngx_queue_empty(&ctx->frames)) { + q = ngx_queue_head(&ctx->frames); + ngx_queue_remove(q); + + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + ngx_quic_free_frame(c, f); + } + + return 0; + } + + ngx_quic_init_packet(c, ctx, &pkt, qc->path); + + min_payload = ngx_quic_payload_size(&pkt, min); + max_payload = ngx_quic_payload_size(&pkt, max); + + /* RFC 9001, 5.4.2. Header Protection Sample */ + pad = 4 - pkt.num_len; + min_payload = ngx_max(min_payload, pad); + + if (min_payload > max_payload) { + return 0; + } + + now = ngx_current_msec; + nframes = 0; + p = src; + len = 0; + + for (q = ngx_queue_head(&ctx->frames); + q != ngx_queue_sentinel(&ctx->frames); + q = ngx_queue_next(q)) + { + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + if (len >= max_payload) { + break; + } + + if (len + f->len > max_payload) { + rc = ngx_quic_split_frame(c, f, max_payload - len); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + break; + } + } + + if (f->need_ack) { + pkt.need_ack = 1; + } + + f->pnum = ctx->pnum; + f->send_time = now; + f->plen = 0; + + ngx_quic_log_frame(c->log, f, 1); + + flen = ngx_quic_create_frame(p, f); + if (flen == -1) { + return NGX_ERROR; + } + + len += flen; + p += flen; + + nframes++; + } + + if (nframes == 0) { + return 0; + } + + if (len < min_payload) { + ngx_memset(p, NGX_QUIC_FT_PADDING, min_payload - len); + len = min_payload; + } + + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = data; + + ngx_quic_log_packet(c->log, &pkt); + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + + ctx->pnum++; + + if (pkt.need_ack) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + f->plen = res.len; + } + + while (nframes--) { + q = ngx_queue_head(&ctx->frames); + f = ngx_queue_data(q, ngx_quic_frame_t, queue); + + f->pkt_need_ack = pkt.need_ack; + + ngx_queue_remove(q); + ngx_queue_insert_tail(&ctx->sending, q); + } + + return res.len; +} + + +static void +ngx_quic_init_packet(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + ngx_quic_header_t *pkt, ngx_quic_path_t *path) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_memzero(pkt, sizeof(ngx_quic_header_t)); + + pkt->flags = NGX_QUIC_PKT_FIXED_BIT; + + if (ctx->level == ssl_encryption_initial) { + pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_INITIAL; + + } else if (ctx->level == ssl_encryption_handshake) { + pkt->flags |= NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_HANDSHAKE; + + } else { + if (qc->key_phase) { + pkt->flags |= NGX_QUIC_PKT_KPHASE; + } + } + + pkt->dcid.data = path->cid->id; + pkt->dcid.len = path->cid->len; + + pkt->scid = qc->tp.initial_scid; + + pkt->version = qc->version; + pkt->log = c->log; + pkt->level = ctx->level; + + pkt->keys = qc->keys; + + ngx_quic_set_packet_number(pkt, ctx); +} + + +static ssize_t +ngx_quic_send(ngx_connection_t *c, u_char *buf, size_t len, + struct sockaddr *sockaddr, socklen_t socklen) +{ + ssize_t n; + struct iovec iov; + struct msghdr msg; +#if (NGX_HAVE_ADDRINFO_CMSG) + struct cmsghdr *cmsg; + char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; +#endif + + ngx_memzero(&msg, sizeof(struct msghdr)); + + iov.iov_len = len; + iov.iov_base = (void *) buf; + + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + + msg.msg_name = sockaddr; + msg.msg_namelen = socklen; + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (c->listening && c->listening->wildcard && c->local_sockaddr) { + + msg.msg_control = msg_control; + msg.msg_controllen = sizeof(msg_control); + ngx_memzero(msg_control, sizeof(msg_control)); + + cmsg = CMSG_FIRSTHDR(&msg); + + msg.msg_controllen = ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); + } +#endif + + n = ngx_sendmsg(c, &msg, 0); + if (n < 0) { + return n; + } + + c->sent += n; + + return n; +} + + +static void +ngx_quic_set_packet_number(ngx_quic_header_t *pkt, ngx_quic_send_ctx_t *ctx) +{ + uint64_t delta; + + delta = ctx->pnum - ctx->largest_ack; + pkt->number = ctx->pnum; + + if (delta <= 0x7F) { + pkt->num_len = 1; + pkt->trunc = ctx->pnum & 0xff; + + } else if (delta <= 0x7FFF) { + pkt->num_len = 2; + pkt->flags |= 0x1; + pkt->trunc = ctx->pnum & 0xffff; + + } else if (delta <= 0x7FFFFF) { + pkt->num_len = 3; + pkt->flags |= 0x2; + pkt->trunc = ctx->pnum & 0xffffff; + + } else { + pkt->num_len = 4; + pkt->flags |= 0x3; + pkt->trunc = ctx->pnum & 0xffffffff; + } +} + + +ngx_int_t +ngx_quic_negotiate_version(ngx_connection_t *c, ngx_quic_header_t *inpkt) +{ + size_t len; + ngx_quic_header_t pkt; + static u_char buf[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sending version negotiation packet"); + + pkt.log = c->log; + pkt.flags = NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_FIXED_BIT; + pkt.dcid = inpkt->scid; + pkt.scid = inpkt->dcid; + + len = ngx_quic_create_version_negotiation(&pkt, buf); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic vnego packet to send len:%uz %*xs", len, len, buf); +#endif + + (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); + + return NGX_DONE; +} + + +ngx_int_t +ngx_quic_send_stateless_reset(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *pkt) +{ + u_char *token; + size_t len, max; + uint16_t rndbytes; + u_char buf[NGX_QUIC_MAX_SR_PACKET]; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic handle stateless reset output"); + + if (pkt->len <= NGX_QUIC_MIN_PKT_LEN) { + return NGX_DECLINED; + } + + if (pkt->len <= NGX_QUIC_MIN_SR_PACKET) { + len = pkt->len - 1; + + } else { + max = ngx_min(NGX_QUIC_MAX_SR_PACKET, pkt->len * 3); + + if (RAND_bytes((u_char *) &rndbytes, sizeof(rndbytes)) != 1) { + return NGX_ERROR; + } + + len = (rndbytes % (max - NGX_QUIC_MIN_SR_PACKET + 1)) + + NGX_QUIC_MIN_SR_PACKET; + } + + if (RAND_bytes(buf, len - NGX_QUIC_SR_TOKEN_LEN) != 1) { + return NGX_ERROR; + } + + buf[0] &= ~NGX_QUIC_PKT_LONG; + buf[0] |= NGX_QUIC_PKT_FIXED_BIT; + + token = &buf[len - NGX_QUIC_SR_TOKEN_LEN]; + + if (ngx_quic_new_sr_token(c, &pkt->dcid, conf->sr_token_key, token) + != NGX_OK) + { + return NGX_ERROR; + } + + (void) ngx_quic_send(c, buf, len, c->sockaddr, c->socklen); + + return NGX_DECLINED; +} + + +ngx_int_t +ngx_quic_send_cc(ngx_connection_t *c) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->draining) { + return NGX_OK; + } + + if (qc->closing + && ngx_current_msec - qc->last_cc < NGX_QUIC_CC_MIN_INTERVAL) + { + /* dot not send CC too often */ + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = qc->error_level; + frame->type = qc->error_app ? NGX_QUIC_FT_CONNECTION_CLOSE_APP + : NGX_QUIC_FT_CONNECTION_CLOSE; + frame->u.close.error_code = qc->error; + frame->u.close.frame_type = qc->error_ftype; + + if (qc->error_reason) { + frame->u.close.reason.len = ngx_strlen(qc->error_reason); + frame->u.close.reason.data = (u_char *) qc->error_reason; + } + + frame->ignore_congestion = 1; + + qc->last_cc = ngx_current_msec; + + return ngx_quic_frame_sendto(c, frame, 0, qc->path); +} + + +ngx_int_t +ngx_quic_send_early_cc(ngx_connection_t *c, ngx_quic_header_t *inpkt, + ngx_uint_t err, const char *reason) +{ + ssize_t len; + ngx_str_t res; + ngx_quic_keys_t keys; + ngx_quic_frame_t frame; + ngx_quic_header_t pkt; + + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + ngx_memzero(&frame, sizeof(ngx_quic_frame_t)); + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + + frame.level = inpkt->level; + frame.type = NGX_QUIC_FT_CONNECTION_CLOSE; + frame.u.close.error_code = err; + + frame.u.close.reason.data = (u_char *) reason; + frame.u.close.reason.len = ngx_strlen(reason); + + ngx_quic_log_frame(c->log, &frame, 1); + + len = ngx_quic_create_frame(NULL, &frame); + if (len > NGX_QUIC_MAX_UDP_PAYLOAD_SIZE) { + return NGX_ERROR; + } + + len = ngx_quic_create_frame(src, &frame); + if (len == -1) { + return NGX_ERROR; + } + + ngx_memzero(&keys, sizeof(ngx_quic_keys_t)); + + pkt.keys = &keys; + + if (ngx_quic_keys_set_initial_secret(pkt.keys, &inpkt->dcid, c->log) + != NGX_OK) + { + return NGX_ERROR; + } + + pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG + | NGX_QUIC_PKT_INITIAL; + + pkt.num_len = 1; + /* + * pkt.num = 0; + * pkt.trunc = 0; + */ + + pkt.version = inpkt->version; + pkt.log = c->log; + pkt.level = inpkt->level; + pkt.dcid = inpkt->scid; + pkt.scid = inpkt->dcid; + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = dst; + + ngx_quic_log_packet(c->log, &pkt); + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + ngx_quic_keys_cleanup(pkt.keys); + return NGX_ERROR; + } + + if (ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen) < 0) { + ngx_quic_keys_cleanup(pkt.keys); + return NGX_ERROR; + } + + ngx_quic_keys_cleanup(pkt.keys); + + return NGX_DONE; +} + + +ngx_int_t +ngx_quic_send_retry(ngx_connection_t *c, ngx_quic_conf_t *conf, + ngx_quic_header_t *inpkt) +{ + time_t expires; + ssize_t len; + ngx_str_t res, token; + ngx_quic_header_t pkt; + + u_char buf[NGX_QUIC_RETRY_BUFFER_SIZE]; + u_char dcid[NGX_QUIC_SERVER_CID_LEN]; + u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE]; + + expires = ngx_time() + NGX_QUIC_RETRY_TOKEN_LIFETIME; + + token.data = tbuf; + token.len = NGX_QUIC_TOKEN_BUF_SIZE; + + if (ngx_quic_new_token(c->log, c->sockaddr, c->socklen, conf->av_token_key, + &token, &inpkt->dcid, expires, 1) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_memzero(&pkt, sizeof(ngx_quic_header_t)); + pkt.flags = NGX_QUIC_PKT_FIXED_BIT | NGX_QUIC_PKT_LONG | NGX_QUIC_PKT_RETRY; + pkt.version = inpkt->version; + pkt.log = c->log; + + pkt.odcid = inpkt->dcid; + pkt.dcid = inpkt->scid; + + /* TODO: generate routable dcid */ + if (RAND_bytes(dcid, NGX_QUIC_SERVER_CID_LEN) != 1) { + return NGX_ERROR; + } + + pkt.scid.len = NGX_QUIC_SERVER_CID_LEN; + pkt.scid.data = dcid; + + pkt.token = token; + + res.data = buf; + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic packet to send len:%uz %xV", res.len, &res); +#endif + + len = ngx_quic_send(c, res.data, res.len, c->sockaddr, c->socklen); + if (len < 0) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic retry packet sent to %xV", &pkt.dcid); + + /* + * RFC 9000, 17.2.5.1. Sending a Retry Packet + * + * A server MUST NOT send more than one Retry + * packet in response to a single UDP datagram. + * NGX_DONE will stop quic_input() from processing further + */ + return NGX_DONE; +} + + +ngx_int_t +ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path) +{ + time_t expires; + ngx_str_t token; + ngx_chain_t *out; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + u_char tbuf[NGX_QUIC_TOKEN_BUF_SIZE]; + + qc = ngx_quic_get_connection(c); + + expires = ngx_time() + NGX_QUIC_NEW_TOKEN_LIFETIME; + + token.data = tbuf; + token.len = NGX_QUIC_TOKEN_BUF_SIZE; + + if (ngx_quic_new_token(c->log, path->sockaddr, path->socklen, + qc->conf->av_token_key, &token, NULL, expires, 0) + != NGX_OK) + { + return NGX_ERROR; + } + + out = ngx_quic_copy_buffer(c, token.data, token.len); + if (out == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_NEW_TOKEN; + frame->data = out; + frame->u.token.length = token.len; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_send_ack(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx) +{ + size_t len, left; + uint64_t ack_delay; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *cl, **ll; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ack_delay = ngx_current_msec - ctx->largest_received; + ack_delay *= 1000; + ack_delay >>= qc->tp.ack_delay_exponent; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + ll = &frame->data; + b = NULL; + + for (i = 0; i < ctx->nranges; i++) { + len = ngx_quic_create_ack_range(NULL, ctx->ranges[i].gap, + ctx->ranges[i].range); + + left = b ? b->end - b->last : 0; + + if (left < len) { + cl = ngx_quic_alloc_chain(c); + if (cl == NULL) { + return NGX_ERROR; + } + + *ll = cl; + ll = &cl->next; + + b = cl->buf; + left = b->end - b->last; + + if (left < len) { + return NGX_ERROR; + } + } + + b->last += ngx_quic_create_ack_range(b->last, ctx->ranges[i].gap, + ctx->ranges[i].range); + + frame->u.ack.ranges_length += len; + } + + *ll = NULL; + + frame->level = ctx->level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = ctx->largest_range; + frame->u.ack.delay = ack_delay; + frame->u.ack.range_count = ctx->nranges; + frame->u.ack.first_range = ctx->first_range; + frame->len = ngx_quic_create_frame(NULL, frame); + + ngx_queue_insert_head(&ctx->frames, &frame->queue); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_send_ack_range(ngx_connection_t *c, ngx_quic_send_ctx_t *ctx, + uint64_t smallest, uint64_t largest) +{ + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ctx->level; + frame->type = NGX_QUIC_FT_ACK; + frame->u.ack.largest = largest; + frame->u.ack.delay = 0; + frame->u.ack.range_count = 0; + frame->u.ack.first_range = largest - smallest; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, + size_t min, ngx_quic_path_t *path) +{ + size_t max, max_payload, min_payload, pad; + ssize_t len, sent; + ngx_str_t res; + ngx_msec_t now; + ngx_quic_header_t pkt; + ngx_quic_send_ctx_t *ctx; + ngx_quic_congestion_t *cg; + ngx_quic_connection_t *qc; + + static u_char src[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + static u_char dst[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + + qc = ngx_quic_get_connection(c); + cg = &qc->congestion; + ctx = ngx_quic_get_send_ctx(qc, frame->level); + + now = ngx_current_msec; + + max = ngx_quic_path_limit(c, path, path->mtu); + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic sendto %s packet max:%uz min:%uz", + ngx_quic_level_name(ctx->level), max, min); + + if (cg->in_flight >= cg->window && !frame->ignore_congestion) { + ngx_quic_free_frame(c, frame); + return NGX_AGAIN; + } + + ngx_quic_init_packet(c, ctx, &pkt, path); + + min_payload = ngx_quic_payload_size(&pkt, min); + max_payload = ngx_quic_payload_size(&pkt, max); + + /* RFC 9001, 5.4.2. Header Protection Sample */ + pad = 4 - pkt.num_len; + min_payload = ngx_max(min_payload, pad); + + if (min_payload > max_payload) { + ngx_quic_free_frame(c, frame); + return NGX_AGAIN; + } + +#if (NGX_DEBUG) + frame->pnum = pkt.number; +#endif + + ngx_quic_log_frame(c->log, frame, 1); + + len = ngx_quic_create_frame(NULL, frame); + if ((size_t) len > max_payload) { + ngx_quic_free_frame(c, frame); + return NGX_AGAIN; + } + + len = ngx_quic_create_frame(src, frame); + if (len == -1) { + ngx_quic_free_frame(c, frame); + return NGX_ERROR; + } + + if (len < (ssize_t) min_payload) { + ngx_memset(src + len, NGX_QUIC_FT_PADDING, min_payload - len); + len = min_payload; + } + + pkt.payload.data = src; + pkt.payload.len = len; + + res.data = dst; + + ngx_quic_log_packet(c->log, &pkt); + + if (ngx_quic_encrypt(&pkt, &res) != NGX_OK) { + ngx_quic_free_frame(c, frame); + return NGX_ERROR; + } + + frame->pnum = ctx->pnum; + frame->send_time = now; + frame->plen = res.len; + + ctx->pnum++; + + sent = ngx_quic_send(c, res.data, res.len, path->sockaddr, path->socklen); + if (sent < 0) { + ngx_quic_free_frame(c, frame); + return sent; + } + + path->sent += sent; + + if (frame->need_ack && !qc->closing) { + ngx_queue_insert_tail(&ctx->sent, &frame->queue); + + cg->in_flight += frame->plen; + + } else { + ngx_quic_free_frame(c, frame); + return NGX_OK; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic congestion send if:%uz", cg->in_flight); + + if (!qc->send_timer_set) { + qc->send_timer_set = 1; + ngx_add_timer(c->read, qc->tp.max_idle_timeout); + } + + ngx_quic_set_lost_timer(c); + + return NGX_OK; +} + + +size_t +ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, size_t size) +{ + off_t max; + + if (!path->validated) { + max = path->received * 3; + max = (path->sent >= max) ? 0 : max - path->sent; + + if ((off_t) size > max) { + return max; + } + } + + return size; +} diff --git a/src/event/quic/ngx_event_quic_output.h b/src/event/quic/ngx_event_quic_output.h new file mode 100644 index 0000000..52d8a37 --- /dev/null +++ b/src/event/quic/ngx_event_quic_output.h @@ -0,0 +1,40 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ +#define _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +ngx_int_t ngx_quic_output(ngx_connection_t *c); + +ngx_int_t ngx_quic_negotiate_version(ngx_connection_t *c, + ngx_quic_header_t *inpkt); + +ngx_int_t ngx_quic_send_stateless_reset(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_send_cc(ngx_connection_t *c); +ngx_int_t ngx_quic_send_early_cc(ngx_connection_t *c, + ngx_quic_header_t *inpkt, ngx_uint_t err, const char *reason); + +ngx_int_t ngx_quic_send_retry(ngx_connection_t *c, + ngx_quic_conf_t *conf, ngx_quic_header_t *pkt); +ngx_int_t ngx_quic_send_new_token(ngx_connection_t *c, ngx_quic_path_t *path); + +ngx_int_t ngx_quic_send_ack(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx); +ngx_int_t ngx_quic_send_ack_range(ngx_connection_t *c, + ngx_quic_send_ctx_t *ctx, uint64_t smallest, uint64_t largest); + +ngx_int_t ngx_quic_frame_sendto(ngx_connection_t *c, ngx_quic_frame_t *frame, + size_t min, ngx_quic_path_t *path); +size_t ngx_quic_path_limit(ngx_connection_t *c, ngx_quic_path_t *path, + size_t size); + +#endif /* _NGX_EVENT_QUIC_OUTPUT_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_protection.c b/src/event/quic/ngx_event_quic_protection.c new file mode 100644 index 0000000..8223626 --- /dev/null +++ b/src/event/quic/ngx_event_quic_protection.c @@ -0,0 +1,1243 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + + +/* RFC 9001, 5.4.1. Header Protection Application: 5-byte mask */ +#define NGX_QUIC_HP_LEN 5 + +#define NGX_QUIC_AES_128_KEY_LEN 16 + +#define NGX_QUIC_INITIAL_CIPHER TLS1_3_CK_AES_128_GCM_SHA256 + + +static ngx_int_t ngx_hkdf_expand(u_char *out_key, size_t out_len, + const EVP_MD *digest, const u_char *prk, size_t prk_len, + const u_char *info, size_t info_len); +static ngx_int_t ngx_hkdf_extract(u_char *out_key, size_t *out_len, + const EVP_MD *digest, const u_char *secret, size_t secret_len, + const u_char *salt, size_t salt_len); + +static uint64_t ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, + uint64_t *largest_pn); + +static ngx_int_t ngx_quic_crypto_open(ngx_quic_secret_t *s, ngx_str_t *out, + u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); +#ifndef OPENSSL_IS_BORINGSSL +static ngx_int_t ngx_quic_crypto_common(ngx_quic_secret_t *s, ngx_str_t *out, + u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); +#endif + +static ngx_int_t ngx_quic_crypto_hp_init(const EVP_CIPHER *cipher, + ngx_quic_secret_t *s, ngx_log_t *log); +static ngx_int_t ngx_quic_crypto_hp(ngx_quic_secret_t *s, + u_char *out, u_char *in, ngx_log_t *log); +static void ngx_quic_crypto_hp_cleanup(ngx_quic_secret_t *s); + +static ngx_int_t ngx_quic_create_packet(ngx_quic_header_t *pkt, + ngx_str_t *res); +static ngx_int_t ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, + ngx_str_t *res); + + +ngx_int_t +ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers) +{ + ngx_int_t len; + + switch (id) { + + case TLS1_3_CK_AES_128_GCM_SHA256: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_aes_128_gcm(); +#else + ciphers->c = EVP_aes_128_gcm(); +#endif + ciphers->hp = EVP_aes_128_ctr(); + ciphers->d = EVP_sha256(); + len = 16; + break; + + case TLS1_3_CK_AES_256_GCM_SHA384: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_aes_256_gcm(); +#else + ciphers->c = EVP_aes_256_gcm(); +#endif + ciphers->hp = EVP_aes_256_ctr(); + ciphers->d = EVP_sha384(); + len = 32; + break; + + case TLS1_3_CK_CHACHA20_POLY1305_SHA256: +#ifdef OPENSSL_IS_BORINGSSL + ciphers->c = EVP_aead_chacha20_poly1305(); +#else + ciphers->c = EVP_chacha20_poly1305(); +#endif +#ifdef OPENSSL_IS_BORINGSSL + ciphers->hp = (const EVP_CIPHER *) EVP_aead_chacha20_poly1305(); +#else + ciphers->hp = EVP_chacha20(); +#endif + ciphers->d = EVP_sha256(); + len = 32; + break; + +#ifndef OPENSSL_IS_BORINGSSL + case TLS1_3_CK_AES_128_CCM_SHA256: + ciphers->c = EVP_aes_128_ccm(); + ciphers->hp = EVP_aes_128_ctr(); + ciphers->d = EVP_sha256(); + len = 16; + break; +#endif + + default: + return NGX_ERROR; + } + + return len; +} + + +ngx_int_t +ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, ngx_str_t *secret, + ngx_log_t *log) +{ + size_t is_len; + uint8_t is[SHA256_DIGEST_LENGTH]; + ngx_str_t iss; + ngx_uint_t i; + const EVP_MD *digest; + ngx_quic_md_t client_key, server_key; + ngx_quic_hkdf_t seq[8]; + ngx_quic_secret_t *client, *server; + ngx_quic_ciphers_t ciphers; + + static const uint8_t salt[20] = + "\x38\x76\x2c\xf7\xf5\x59\x34\xb3\x4d\x17" + "\x9a\xe6\xa4\xc8\x0c\xad\xcc\xbb\x7f\x0a"; + + client = &keys->secrets[ssl_encryption_initial].client; + server = &keys->secrets[ssl_encryption_initial].server; + + /* + * RFC 9001, section 5. Packet Protection + * + * Initial packets use AEAD_AES_128_GCM. The hash function + * for HKDF when deriving initial secrets and keys is SHA-256. + */ + + digest = EVP_sha256(); + is_len = SHA256_DIGEST_LENGTH; + + if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, + salt, sizeof(salt)) + != NGX_OK) + { + return NGX_ERROR; + } + + iss.len = is_len; + iss.data = is; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, + "quic ngx_quic_set_initial_secret"); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0, + "quic salt len:%uz %*xs", sizeof(salt), sizeof(salt), salt); + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, log, 0, + "quic initial secret len:%uz %*xs", is_len, is_len, is); +#endif + + client->secret.len = SHA256_DIGEST_LENGTH; + server->secret.len = SHA256_DIGEST_LENGTH; + + client_key.len = NGX_QUIC_AES_128_KEY_LEN; + server_key.len = NGX_QUIC_AES_128_KEY_LEN; + + client->hp.len = NGX_QUIC_AES_128_KEY_LEN; + server->hp.len = NGX_QUIC_AES_128_KEY_LEN; + + client->iv.len = NGX_QUIC_IV_LEN; + server->iv.len = NGX_QUIC_IV_LEN; + + /* labels per RFC 9001, 5.1. Packet Protection Keys */ + ngx_quic_hkdf_set(&seq[0], "tls13 client in", &client->secret, &iss); + ngx_quic_hkdf_set(&seq[1], "tls13 quic key", &client_key, &client->secret); + ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", &client->iv, &client->secret); + ngx_quic_hkdf_set(&seq[3], "tls13 quic hp", &client->hp, &client->secret); + ngx_quic_hkdf_set(&seq[4], "tls13 server in", &server->secret, &iss); + ngx_quic_hkdf_set(&seq[5], "tls13 quic key", &server_key, &server->secret); + ngx_quic_hkdf_set(&seq[6], "tls13 quic iv", &server->iv, &server->secret); + ngx_quic_hkdf_set(&seq[7], "tls13 quic hp", &server->hp, &server->secret); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], digest, log) != NGX_OK) { + return NGX_ERROR; + } + } + + if (ngx_quic_ciphers(NGX_QUIC_INITIAL_CIPHER, &ciphers) == NGX_ERROR) { + return NGX_ERROR; + } + + if (ngx_quic_crypto_init(ciphers.c, client, &client_key, 0, log) + == NGX_ERROR) + { + return NGX_ERROR; + } + + if (ngx_quic_crypto_init(ciphers.c, server, &server_key, 1, log) + == NGX_ERROR) + { + goto failed; + } + + if (ngx_quic_crypto_hp_init(ciphers.hp, client, log) == NGX_ERROR) { + goto failed; + } + + if (ngx_quic_crypto_hp_init(ciphers.hp, server, log) == NGX_ERROR) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_quic_keys_cleanup(keys); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_quic_hkdf_expand(ngx_quic_hkdf_t *h, const EVP_MD *digest, ngx_log_t *log) +{ + size_t info_len; + uint8_t *p; + uint8_t info[20]; + + info_len = 2 + 1 + h->label_len + 1; + + info[0] = 0; + info[1] = h->out_len; + info[2] = h->label_len; + + p = ngx_cpymem(&info[3], h->label, h->label_len); + *p = '\0'; + + if (ngx_hkdf_expand(h->out, h->out_len, digest, + h->prk, h->prk_len, info, info_len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_expand(%*s) failed", h->label_len, h->label); + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, log, 0, + "quic expand \"%*s\" len:%uz %*xs", + h->label_len, h->label, h->out_len, h->out_len, h->out); +#endif + + return NGX_OK; +} + + +static ngx_int_t +ngx_hkdf_expand(u_char *out_key, size_t out_len, const EVP_MD *digest, + const uint8_t *prk, size_t prk_len, const u_char *info, size_t info_len) +{ +#ifdef OPENSSL_IS_BORINGSSL + + if (HKDF_expand(out_key, out_len, digest, prk, prk_len, info, info_len) + == 0) + { + return NGX_ERROR; + } + + return NGX_OK; + +#else + + EVP_PKEY_CTX *pctx; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + return NGX_ERROR; + } + + if (EVP_PKEY_derive_init(pctx) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXPAND_ONLY) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, prk, prk_len) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_add1_hkdf_info(pctx, info, info_len) <= 0) { + goto failed; + } + + if (EVP_PKEY_derive(pctx, out_key, &out_len) <= 0) { + goto failed; + } + + EVP_PKEY_CTX_free(pctx); + + return NGX_OK; + +failed: + + EVP_PKEY_CTX_free(pctx); + + return NGX_ERROR; + +#endif +} + + +static ngx_int_t +ngx_hkdf_extract(u_char *out_key, size_t *out_len, const EVP_MD *digest, + const u_char *secret, size_t secret_len, const u_char *salt, + size_t salt_len) +{ +#ifdef OPENSSL_IS_BORINGSSL + + if (HKDF_extract(out_key, out_len, digest, secret, secret_len, salt, + salt_len) + == 0) + { + return NGX_ERROR; + } + + return NGX_OK; + +#else + + EVP_PKEY_CTX *pctx; + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_HKDF, NULL); + if (pctx == NULL) { + return NGX_ERROR; + } + + if (EVP_PKEY_derive_init(pctx) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_hkdf_mode(pctx, EVP_PKEY_HKDEF_MODE_EXTRACT_ONLY) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set_hkdf_md(pctx, digest) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set1_hkdf_key(pctx, secret, secret_len) <= 0) { + goto failed; + } + + if (EVP_PKEY_CTX_set1_hkdf_salt(pctx, salt, salt_len) <= 0) { + goto failed; + } + + if (EVP_PKEY_derive(pctx, out_key, out_len) <= 0) { + goto failed; + } + + EVP_PKEY_CTX_free(pctx); + + return NGX_OK; + +failed: + + EVP_PKEY_CTX_free(pctx); + + return NGX_ERROR; + +#endif +} + + +ngx_int_t +ngx_quic_crypto_init(const ngx_quic_cipher_t *cipher, ngx_quic_secret_t *s, + ngx_quic_md_t *key, ngx_int_t enc, ngx_log_t *log) +{ + +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX *ctx; + + ctx = EVP_AEAD_CTX_new(cipher, key->data, key->len, + EVP_AEAD_DEFAULT_TAG_LENGTH); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_new() failed"); + return NGX_ERROR; + } +#else + EVP_CIPHER_CTX *ctx; + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, enc) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherInit_ex() failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_mode(cipher) == EVP_CIPH_CCM_MODE + && EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, NGX_QUIC_TAG_LEN, + NULL) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_SET_TAG) failed"); + return NGX_ERROR; + } + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, s->iv.len, NULL) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_SET_IVLEN) failed"); + return NGX_ERROR; + } + + if (EVP_CipherInit_ex(ctx, NULL, NULL, key->data, NULL, enc) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherInit_ex() failed"); + return NGX_ERROR; + } +#endif + + s->ctx = ctx; + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_crypto_open(ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, + ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) +{ +#ifdef OPENSSL_IS_BORINGSSL + if (EVP_AEAD_CTX_open(s->ctx, out->data, &out->len, out->len, nonce, + s->iv.len, in->data, in->len, ad->data, ad->len) + != 1) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_open() failed"); + return NGX_ERROR; + } + + return NGX_OK; +#else + return ngx_quic_crypto_common(s, out, nonce, in, ad, log); +#endif +} + + +ngx_int_t +ngx_quic_crypto_seal(ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, + ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) +{ +#ifdef OPENSSL_IS_BORINGSSL + if (EVP_AEAD_CTX_seal(s->ctx, out->data, &out->len, out->len, nonce, + s->iv.len, in->data, in->len, ad->data, ad->len) + != 1) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_AEAD_CTX_seal() failed"); + return NGX_ERROR; + } + + return NGX_OK; +#else + return ngx_quic_crypto_common(s, out, nonce, in, ad, log); +#endif +} + + +#ifndef OPENSSL_IS_BORINGSSL + +static ngx_int_t +ngx_quic_crypto_common(ngx_quic_secret_t *s, ngx_str_t *out, u_char *nonce, + ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log) +{ + int len, enc; + ngx_quic_crypto_ctx_t *ctx; + + ctx = s->ctx; + enc = EVP_CIPHER_CTX_encrypting(ctx); + + if (EVP_CipherInit_ex(ctx, NULL, NULL, NULL, nonce, enc) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherInit_ex() failed"); + return NGX_ERROR; + } + + if (enc == 0) { + in->len -= NGX_QUIC_TAG_LEN; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, NGX_QUIC_TAG_LEN, + in->data + in->len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_SET_TAG) failed"); + return NGX_ERROR; + } + } + + if (EVP_CIPHER_mode(EVP_CIPHER_CTX_cipher(ctx)) == EVP_CIPH_CCM_MODE + && EVP_CipherUpdate(ctx, NULL, &len, NULL, in->len) != 1) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_CipherUpdate(ctx, NULL, &len, ad->data, ad->len) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherUpdate() failed"); + return NGX_ERROR; + } + + if (EVP_CipherUpdate(ctx, out->data, &len, in->data, in->len) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherUpdate() failed"); + return NGX_ERROR; + } + + out->len = len; + + if (EVP_CipherFinal_ex(ctx, out->data + out->len, &len) <= 0) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CipherFinal_ex failed"); + return NGX_ERROR; + } + + out->len += len; + + if (enc == 1) { + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, NGX_QUIC_TAG_LEN, + out->data + out->len) + == 0) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "EVP_CIPHER_CTX_ctrl(EVP_CTRL_AEAD_GET_TAG) failed"); + return NGX_ERROR; + } + + out->len += NGX_QUIC_TAG_LEN; + } + + return NGX_OK; +} + +#endif + + +void +ngx_quic_crypto_cleanup(ngx_quic_secret_t *s) +{ + if (s->ctx) { +#ifdef OPENSSL_IS_BORINGSSL + EVP_AEAD_CTX_free(s->ctx); +#else + EVP_CIPHER_CTX_free(s->ctx); +#endif + s->ctx = NULL; + } +} + + +static ngx_int_t +ngx_quic_crypto_hp_init(const EVP_CIPHER *cipher, ngx_quic_secret_t *s, + ngx_log_t *log) +{ + EVP_CIPHER_CTX *ctx; + +#ifdef OPENSSL_IS_BORINGSSL + if (cipher == (EVP_CIPHER *) EVP_aead_chacha20_poly1305()) { + /* no EVP interface */ + s->hp_ctx = NULL; + return NGX_OK; + } +#endif + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_CIPHER_CTX_new() failed"); + return NGX_ERROR; + } + + if (EVP_EncryptInit_ex(ctx, cipher, NULL, s->hp.data, NULL) != 1) { + EVP_CIPHER_CTX_free(ctx); + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + return NGX_ERROR; + } + + s->hp_ctx = ctx; + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_crypto_hp(ngx_quic_secret_t *s, u_char *out, u_char *in, + ngx_log_t *log) +{ + int outlen; + EVP_CIPHER_CTX *ctx; + u_char zero[NGX_QUIC_HP_LEN] = {0}; + + ctx = s->hp_ctx; + +#ifdef OPENSSL_IS_BORINGSSL + uint32_t cnt; + + if (ctx == NULL) { + ngx_memcpy(&cnt, in, sizeof(uint32_t)); + CRYPTO_chacha_20(out, zero, NGX_QUIC_HP_LEN, s->hp.data, &in[4], cnt); + return NGX_OK; + } +#endif + + if (EVP_EncryptInit_ex(ctx, NULL, NULL, NULL, in) != 1) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptInit_ex() failed"); + return NGX_ERROR; + } + + if (!EVP_EncryptUpdate(ctx, out, &outlen, zero, NGX_QUIC_HP_LEN)) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptUpdate() failed"); + return NGX_ERROR; + } + + if (!EVP_EncryptFinal_ex(ctx, out + NGX_QUIC_HP_LEN, &outlen)) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "EVP_EncryptFinal_Ex() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_quic_crypto_hp_cleanup(ngx_quic_secret_t *s) +{ + if (s->hp_ctx) { + EVP_CIPHER_CTX_free(s->hp_ctx); + s->hp_ctx = NULL; + } +} + + +ngx_int_t +ngx_quic_keys_set_encryption_secret(ngx_log_t *log, ngx_uint_t is_write, + ngx_quic_keys_t *keys, enum ssl_encryption_level_t level, + const SSL_CIPHER *cipher, const uint8_t *secret, size_t secret_len) +{ + ngx_int_t key_len; + ngx_str_t secret_str; + ngx_uint_t i; + ngx_quic_md_t key; + ngx_quic_hkdf_t seq[3]; + ngx_quic_secret_t *peer_secret; + ngx_quic_ciphers_t ciphers; + + peer_secret = is_write ? &keys->secrets[level].server + : &keys->secrets[level].client; + + keys->cipher = SSL_CIPHER_get_id(cipher); + + key_len = ngx_quic_ciphers(keys->cipher, &ciphers); + + if (key_len == NGX_ERROR) { + ngx_ssl_error(NGX_LOG_INFO, log, 0, "unexpected cipher"); + return NGX_ERROR; + } + + if (sizeof(peer_secret->secret.data) < secret_len) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + "unexpected secret len: %uz", secret_len); + return NGX_ERROR; + } + + peer_secret->secret.len = secret_len; + ngx_memcpy(peer_secret->secret.data, secret, secret_len); + + key.len = key_len; + peer_secret->iv.len = NGX_QUIC_IV_LEN; + peer_secret->hp.len = key_len; + + secret_str.len = secret_len; + secret_str.data = (u_char *) secret; + + ngx_quic_hkdf_set(&seq[0], "tls13 quic key", &key, &secret_str); + ngx_quic_hkdf_set(&seq[1], "tls13 quic iv", &peer_secret->iv, &secret_str); + ngx_quic_hkdf_set(&seq[2], "tls13 quic hp", &peer_secret->hp, &secret_str); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, log) != NGX_OK) { + return NGX_ERROR; + } + } + + if (ngx_quic_crypto_init(ciphers.c, peer_secret, &key, is_write, log) + == NGX_ERROR) + { + return NGX_ERROR; + } + + if (ngx_quic_crypto_hp_init(ciphers.hp, peer_secret, log) == NGX_ERROR) { + return NGX_ERROR; + } + + ngx_explicit_memzero(key.data, key.len); + + return NGX_OK; +} + + +ngx_uint_t +ngx_quic_keys_available(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level, ngx_uint_t is_write) +{ + if (is_write == 0) { + return keys->secrets[level].client.ctx != NULL; + } + + return keys->secrets[level].server.ctx != NULL; +} + + +void +ngx_quic_keys_discard(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level) +{ + ngx_quic_secret_t *client, *server; + + client = &keys->secrets[level].client; + server = &keys->secrets[level].server; + + ngx_quic_crypto_cleanup(client); + ngx_quic_crypto_cleanup(server); + + ngx_quic_crypto_hp_cleanup(client); + ngx_quic_crypto_hp_cleanup(server); + + ngx_explicit_memzero(client->secret.data, client->secret.len); + ngx_explicit_memzero(server->secret.data, server->secret.len); +} + + +void +ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys) +{ + ngx_quic_secrets_t *current, *next, tmp; + + current = &keys->secrets[ssl_encryption_application]; + next = &keys->next_key; + + ngx_quic_crypto_cleanup(¤t->client); + ngx_quic_crypto_cleanup(¤t->server); + + tmp = *current; + *current = *next; + *next = tmp; +} + + +void +ngx_quic_keys_update(ngx_event_t *ev) +{ + ngx_int_t key_len; + ngx_uint_t i; + ngx_quic_md_t client_key, server_key; + ngx_quic_hkdf_t seq[6]; + ngx_quic_keys_t *keys; + ngx_connection_t *c; + ngx_quic_ciphers_t ciphers; + ngx_quic_secrets_t *current, *next; + ngx_quic_connection_t *qc; + + c = ev->data; + qc = ngx_quic_get_connection(c); + keys = qc->keys; + + current = &keys->secrets[ssl_encryption_application]; + next = &keys->next_key; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic key update"); + + c->log->action = "updating keys"; + + key_len = ngx_quic_ciphers(keys->cipher, &ciphers); + + if (key_len == NGX_ERROR) { + goto failed; + } + + client_key.len = key_len; + server_key.len = key_len; + + next->client.secret.len = current->client.secret.len; + next->client.iv.len = NGX_QUIC_IV_LEN; + next->client.hp = current->client.hp; + next->client.hp_ctx = current->client.hp_ctx; + + next->server.secret.len = current->server.secret.len; + next->server.iv.len = NGX_QUIC_IV_LEN; + next->server.hp = current->server.hp; + next->server.hp_ctx = current->server.hp_ctx; + + ngx_quic_hkdf_set(&seq[0], "tls13 quic ku", + &next->client.secret, ¤t->client.secret); + ngx_quic_hkdf_set(&seq[1], "tls13 quic key", + &client_key, &next->client.secret); + ngx_quic_hkdf_set(&seq[2], "tls13 quic iv", + &next->client.iv, &next->client.secret); + ngx_quic_hkdf_set(&seq[3], "tls13 quic ku", + &next->server.secret, ¤t->server.secret); + ngx_quic_hkdf_set(&seq[4], "tls13 quic key", + &server_key, &next->server.secret); + ngx_quic_hkdf_set(&seq[5], "tls13 quic iv", + &next->server.iv, &next->server.secret); + + for (i = 0; i < (sizeof(seq) / sizeof(seq[0])); i++) { + if (ngx_quic_hkdf_expand(&seq[i], ciphers.d, c->log) != NGX_OK) { + goto failed; + } + } + + if (ngx_quic_crypto_init(ciphers.c, &next->client, &client_key, 0, c->log) + == NGX_ERROR) + { + goto failed; + } + + if (ngx_quic_crypto_init(ciphers.c, &next->server, &server_key, 1, c->log) + == NGX_ERROR) + { + goto failed; + } + + ngx_explicit_memzero(current->client.secret.data, + current->client.secret.len); + ngx_explicit_memzero(current->server.secret.data, + current->server.secret.len); + + ngx_explicit_memzero(client_key.data, client_key.len); + ngx_explicit_memzero(server_key.data, server_key.len); + + return; + +failed: + + ngx_quic_close_connection(c, NGX_ERROR); +} + + +void +ngx_quic_keys_cleanup(ngx_quic_keys_t *keys) +{ + ngx_uint_t i; + ngx_quic_secrets_t *next; + + for (i = 0; i < NGX_QUIC_ENCRYPTION_LAST; i++) { + ngx_quic_keys_discard(keys, i); + } + + next = &keys->next_key; + + ngx_quic_crypto_cleanup(&next->client); + ngx_quic_crypto_cleanup(&next->server); + + ngx_explicit_memzero(next->client.secret.data, + next->client.secret.len); + ngx_explicit_memzero(next->server.secret.data, + next->server.secret.len); +} + + +static ngx_int_t +ngx_quic_create_packet(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + u_char *pnp, *sample; + ngx_str_t ad, out; + ngx_uint_t i; + ngx_quic_secret_t *secret; + u_char nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; + + ad.data = res->data; + ad.len = ngx_quic_create_header(pkt, ad.data, &pnp); + + out.len = pkt->payload.len + NGX_QUIC_TAG_LEN; + out.data = res->data + ad.len; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ad len:%uz %xV", ad.len, &ad); +#endif + + secret = &pkt->keys->secrets[pkt->level].server; + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); + ngx_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); + + if (ngx_quic_crypto_seal(secret, &out, nonce, &pkt->payload, &ad, pkt->log) + != NGX_OK) + { + return NGX_ERROR; + } + + sample = &out.data[4 - pkt->num_len]; + if (ngx_quic_crypto_hp(secret, mask, sample, pkt->log) != NGX_OK) { + return NGX_ERROR; + } + + /* RFC 9001, 5.4.1. Header Protection Application */ + ad.data[0] ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); + + for (i = 0; i < pkt->num_len; i++) { + pnp[i] ^= mask[i + 1]; + } + + res->len = ad.len + out.len; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_create_retry_packet(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + u_char *start; + ngx_str_t ad, itag; + ngx_quic_md_t key; + ngx_quic_secret_t secret; + ngx_quic_ciphers_t ciphers; + + /* 5.8. Retry Packet Integrity */ + static u_char key_data[16] = + "\xbe\x0c\x69\x0b\x9f\x66\x57\x5a\x1d\x76\x6b\x54\xe3\x68\xc8\x4e"; + static u_char nonce[NGX_QUIC_IV_LEN] = + "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"; + static ngx_str_t in = ngx_string(""); + + ad.data = res->data; + ad.len = ngx_quic_create_retry_itag(pkt, ad.data, &start); + + itag.data = ad.data + ad.len; + itag.len = NGX_QUIC_TAG_LEN; + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic retry itag len:%uz %xV", ad.len, &ad); +#endif + + if (ngx_quic_ciphers(NGX_QUIC_INITIAL_CIPHER, &ciphers) == NGX_ERROR) { + return NGX_ERROR; + } + + key.len = sizeof(key_data); + ngx_memcpy(key.data, key_data, sizeof(key_data)); + secret.iv.len = NGX_QUIC_IV_LEN; + + if (ngx_quic_crypto_init(ciphers.c, &secret, &key, 1, pkt->log) + == NGX_ERROR) + { + return NGX_ERROR; + } + + if (ngx_quic_crypto_seal(&secret, &itag, nonce, &in, &ad, pkt->log) + != NGX_OK) + { + ngx_quic_crypto_cleanup(&secret); + return NGX_ERROR; + } + + ngx_quic_crypto_cleanup(&secret); + + res->len = itag.data + itag.len - start; + res->data = start; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_derive_key(ngx_log_t *log, const char *label, ngx_str_t *secret, + ngx_str_t *salt, u_char *out, size_t len) +{ + size_t is_len, info_len; + uint8_t *p; + const EVP_MD *digest; + + uint8_t is[SHA256_DIGEST_LENGTH]; + uint8_t info[20]; + + digest = EVP_sha256(); + is_len = SHA256_DIGEST_LENGTH; + + if (ngx_hkdf_extract(is, &is_len, digest, secret->data, secret->len, + salt->data, salt->len) + != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_extract(%s) failed", label); + return NGX_ERROR; + } + + info[0] = 0; + info[1] = len; + info[2] = ngx_strlen(label); + + info_len = 2 + 1 + info[2] + 1; + + if (info_len >= 20) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "ngx_quic_create_key label \"%s\" too long", label); + return NGX_ERROR; + } + + p = ngx_cpymem(&info[3], label, info[2]); + *p = '\0'; + + if (ngx_hkdf_expand(out, len, digest, is, is_len, info, info_len) != NGX_OK) + { + ngx_ssl_error(NGX_LOG_INFO, log, 0, + "ngx_hkdf_expand(%s) failed", label); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static uint64_t +ngx_quic_parse_pn(u_char **pos, ngx_int_t len, u_char *mask, + uint64_t *largest_pn) +{ + u_char *p; + uint64_t truncated_pn, expected_pn, candidate_pn; + uint64_t pn_nbits, pn_win, pn_hwin, pn_mask; + + pn_nbits = ngx_min(len * 8, 62); + + p = *pos; + truncated_pn = *p++ ^ *mask++; + + while (--len) { + truncated_pn = (truncated_pn << 8) + (*p++ ^ *mask++); + } + + *pos = p; + + expected_pn = *largest_pn + 1; + pn_win = 1ULL << pn_nbits; + pn_hwin = pn_win / 2; + pn_mask = pn_win - 1; + + candidate_pn = (expected_pn & ~pn_mask) | truncated_pn; + + if ((int64_t) candidate_pn <= (int64_t) (expected_pn - pn_hwin) + && candidate_pn < (1ULL << 62) - pn_win) + { + candidate_pn += pn_win; + + } else if (candidate_pn > expected_pn + pn_hwin + && candidate_pn >= pn_win) + { + candidate_pn -= pn_win; + } + + *largest_pn = ngx_max((int64_t) *largest_pn, (int64_t) candidate_pn); + + return candidate_pn; +} + + +void +ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn) +{ + nonce[len - 8] ^= (pn >> 56) & 0x3f; + nonce[len - 7] ^= (pn >> 48) & 0xff; + nonce[len - 6] ^= (pn >> 40) & 0xff; + nonce[len - 5] ^= (pn >> 32) & 0xff; + nonce[len - 4] ^= (pn >> 24) & 0xff; + nonce[len - 3] ^= (pn >> 16) & 0xff; + nonce[len - 2] ^= (pn >> 8) & 0xff; + nonce[len - 1] ^= pn & 0xff; +} + + +ngx_int_t +ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res) +{ + if (ngx_quic_pkt_retry(pkt->flags)) { + return ngx_quic_create_retry_packet(pkt, res); + } + + return ngx_quic_create_packet(pkt, res); +} + + +ngx_int_t +ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn) +{ + u_char *p, *sample; + size_t len; + uint64_t pn, lpn; + ngx_int_t pnl; + ngx_str_t in, ad; + ngx_uint_t key_phase; + ngx_quic_secret_t *secret; + uint8_t nonce[NGX_QUIC_IV_LEN], mask[NGX_QUIC_HP_LEN]; + + secret = &pkt->keys->secrets[pkt->level].client; + + p = pkt->raw->pos; + len = pkt->data + pkt->len - p; + + /* + * RFC 9001, 5.4.2. Header Protection Sample + * 5.4.3. AES-Based Header Protection + * 5.4.4. ChaCha20-Based Header Protection + * + * the Packet Number field is assumed to be 4 bytes long + * AES and ChaCha20 algorithms sample 16 bytes + */ + + if (len < NGX_QUIC_TAG_LEN + 4) { + return NGX_DECLINED; + } + + sample = p + 4; + + /* header protection */ + + if (ngx_quic_crypto_hp(secret, mask, sample, pkt->log) != NGX_OK) { + return NGX_DECLINED; + } + + pkt->flags ^= mask[0] & ngx_quic_pkt_hp_mask(pkt->flags); + + if (ngx_quic_short_pkt(pkt->flags)) { + key_phase = (pkt->flags & NGX_QUIC_PKT_KPHASE) != 0; + + if (key_phase != pkt->key_phase) { + if (pkt->keys->next_key.client.ctx != NULL) { + secret = &pkt->keys->next_key.client; + pkt->key_update = 1; + + } else { + /* + * RFC 9001, 6.3. Timing of Receive Key Generation. + * + * Trial decryption to avoid timing side-channel. + */ + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic next key missing"); + } + } + } + + lpn = *largest_pn; + + pnl = (pkt->flags & 0x03) + 1; + pn = ngx_quic_parse_pn(&p, pnl, &mask[1], &lpn); + + pkt->pn = pn; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx clearflags:%xd", pkt->flags); + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx number:%uL len:%xi", pn, pnl); + + /* packet protection */ + + in.data = p; + in.len = len - pnl; + + ad.len = p - pkt->data; + ad.data = pkt->plaintext; + + ngx_memcpy(ad.data, pkt->data, ad.len); + ad.data[0] = pkt->flags; + + do { + ad.data[ad.len - pnl] = pn >> (8 * (pnl - 1)) % 256; + } while (--pnl); + + ngx_memcpy(nonce, secret->iv.data, secret->iv.len); + ngx_quic_compute_nonce(nonce, sizeof(nonce), pn); + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ad len:%uz %xV", ad.len, &ad); +#endif + + pkt->payload.len = in.len - NGX_QUIC_TAG_LEN; + pkt->payload.data = pkt->plaintext + ad.len; + + if (ngx_quic_crypto_open(secret, &pkt->payload, nonce, &in, &ad, pkt->log) + != NGX_OK) + { + return NGX_DECLINED; + } + + if (pkt->payload.len == 0) { + /* + * RFC 9000, 12.4. Frames and Frame Types + * + * An endpoint MUST treat receipt of a packet containing no + * frames as a connection error of type PROTOCOL_VIOLATION. + */ + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic zero-length packet"); + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + + if (pkt->flags & ngx_quic_pkt_rb_mask(pkt->flags)) { + /* + * RFC 9000, Reserved Bits + * + * An endpoint MUST treat receipt of a packet that has + * a non-zero value for these bits, after removing both + * packet and header protection, as a connection error + * of type PROTOCOL_VIOLATION. + */ + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic reserved bit set in packet"); + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + +#if defined(NGX_QUIC_DEBUG_CRYPTO) && defined(NGX_QUIC_DEBUG_PACKETS) + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet payload len:%uz %xV", + pkt->payload.len, &pkt->payload); +#endif + + *largest_pn = lpn; + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_protection.h b/src/event/quic/ngx_event_quic_protection.h new file mode 100644 index 0000000..34cfee6 --- /dev/null +++ b/src/event/quic/ngx_event_quic_protection.h @@ -0,0 +1,120 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ +#define _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + +#include <ngx_event_quic_transport.h> + + +#define NGX_QUIC_ENCRYPTION_LAST ((ssl_encryption_application) + 1) + +/* RFC 5116, 5.1/5.3 and RFC 8439, 2.3/2.5 for all supported ciphers */ +#define NGX_QUIC_IV_LEN 12 +#define NGX_QUIC_TAG_LEN 16 + +/* largest hash used in TLS is SHA-384 */ +#define NGX_QUIC_MAX_MD_SIZE 48 + + +#ifdef OPENSSL_IS_BORINGSSL +#define ngx_quic_cipher_t EVP_AEAD +#define ngx_quic_crypto_ctx_t EVP_AEAD_CTX +#else +#define ngx_quic_cipher_t EVP_CIPHER +#define ngx_quic_crypto_ctx_t EVP_CIPHER_CTX +#endif + + +typedef struct { + size_t len; + u_char data[NGX_QUIC_MAX_MD_SIZE]; +} ngx_quic_md_t; + + +typedef struct { + size_t len; + u_char data[NGX_QUIC_IV_LEN]; +} ngx_quic_iv_t; + + +typedef struct { + ngx_quic_md_t secret; + ngx_quic_iv_t iv; + ngx_quic_md_t hp; + ngx_quic_crypto_ctx_t *ctx; + EVP_CIPHER_CTX *hp_ctx; +} ngx_quic_secret_t; + + +typedef struct { + ngx_quic_secret_t client; + ngx_quic_secret_t server; +} ngx_quic_secrets_t; + + +struct ngx_quic_keys_s { + ngx_quic_secrets_t secrets[NGX_QUIC_ENCRYPTION_LAST]; + ngx_quic_secrets_t next_key; + ngx_uint_t cipher; +}; + + +typedef struct { + const ngx_quic_cipher_t *c; + const EVP_CIPHER *hp; + const EVP_MD *d; +} ngx_quic_ciphers_t; + + +typedef struct { + size_t out_len; + u_char *out; + + size_t prk_len; + const uint8_t *prk; + + size_t label_len; + const u_char *label; +} ngx_quic_hkdf_t; + +#define ngx_quic_hkdf_set(seq, _label, _out, _prk) \ + (seq)->out_len = (_out)->len; (seq)->out = (_out)->data; \ + (seq)->prk_len = (_prk)->len, (seq)->prk = (_prk)->data, \ + (seq)->label_len = (sizeof(_label) - 1); (seq)->label = (u_char *)(_label); + + +ngx_int_t ngx_quic_keys_set_initial_secret(ngx_quic_keys_t *keys, + ngx_str_t *secret, ngx_log_t *log); +ngx_int_t ngx_quic_keys_set_encryption_secret(ngx_log_t *log, + ngx_uint_t is_write, ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +ngx_uint_t ngx_quic_keys_available(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level, ngx_uint_t is_write); +void ngx_quic_keys_discard(ngx_quic_keys_t *keys, + enum ssl_encryption_level_t level); +void ngx_quic_keys_switch(ngx_connection_t *c, ngx_quic_keys_t *keys); +void ngx_quic_keys_update(ngx_event_t *ev); +void ngx_quic_keys_cleanup(ngx_quic_keys_t *keys); +ngx_int_t ngx_quic_encrypt(ngx_quic_header_t *pkt, ngx_str_t *res); +ngx_int_t ngx_quic_decrypt(ngx_quic_header_t *pkt, uint64_t *largest_pn); +void ngx_quic_compute_nonce(u_char *nonce, size_t len, uint64_t pn); +ngx_int_t ngx_quic_ciphers(ngx_uint_t id, ngx_quic_ciphers_t *ciphers); +ngx_int_t ngx_quic_crypto_init(const ngx_quic_cipher_t *cipher, + ngx_quic_secret_t *s, ngx_quic_md_t *key, ngx_int_t enc, ngx_log_t *log); +ngx_int_t ngx_quic_crypto_seal(ngx_quic_secret_t *s, ngx_str_t *out, + u_char *nonce, ngx_str_t *in, ngx_str_t *ad, ngx_log_t *log); +void ngx_quic_crypto_cleanup(ngx_quic_secret_t *s); +ngx_int_t ngx_quic_hkdf_expand(ngx_quic_hkdf_t *hkdf, const EVP_MD *digest, + ngx_log_t *log); + + +#endif /* _NGX_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_socket.c b/src/event/quic/ngx_event_quic_socket.c new file mode 100644 index 0000000..c2bc822 --- /dev/null +++ b/src/event/quic/ngx_event_quic_socket.c @@ -0,0 +1,237 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + + +ngx_int_t +ngx_quic_open_sockets(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_header_t *pkt) +{ + ngx_quic_socket_t *qsock, *tmp; + ngx_quic_client_id_t *cid; + + /* + * qc->path = NULL + * + * qc->nclient_ids = 0 + * qc->nsockets = 0 + * qc->max_retired_seqnum = 0 + * qc->client_seqnum = 0 + */ + + ngx_queue_init(&qc->sockets); + ngx_queue_init(&qc->free_sockets); + + ngx_queue_init(&qc->paths); + ngx_queue_init(&qc->free_paths); + + ngx_queue_init(&qc->client_ids); + ngx_queue_init(&qc->free_client_ids); + + qc->tp.original_dcid.len = pkt->odcid.len; + qc->tp.original_dcid.data = ngx_pstrdup(c->pool, &pkt->odcid); + if (qc->tp.original_dcid.data == NULL) { + return NGX_ERROR; + } + + /* socket to use for further processing (id auto-generated) */ + qsock = ngx_quic_create_socket(c, qc); + if (qsock == NULL) { + return NGX_ERROR; + } + + /* socket is listening at new server id */ + if (ngx_quic_listen(c, qc, qsock) != NGX_OK) { + return NGX_ERROR; + } + + qsock->used = 1; + + qc->tp.initial_scid.len = qsock->sid.len; + qc->tp.initial_scid.data = ngx_pnalloc(c->pool, qsock->sid.len); + if (qc->tp.initial_scid.data == NULL) { + goto failed; + } + ngx_memcpy(qc->tp.initial_scid.data, qsock->sid.id, qsock->sid.len); + + /* for all packets except first, this is set at udp layer */ + c->udp = &qsock->udp; + + /* ngx_quic_get_connection(c) macro is now usable */ + + /* we have a client identified by scid */ + cid = ngx_quic_create_client_id(c, &pkt->scid, 0, NULL); + if (cid == NULL) { + goto failed; + } + + /* path of the first packet is our initial active path */ + qc->path = ngx_quic_new_path(c, c->sockaddr, c->socklen, cid); + if (qc->path == NULL) { + goto failed; + } + + qc->path->tag = NGX_QUIC_PATH_ACTIVE; + + if (pkt->validated) { + qc->path->validated = 1; + } + + ngx_quic_path_dbg(c, "set active", qc->path); + + tmp = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); + if (tmp == NULL) { + goto failed; + } + + tmp->sid.seqnum = NGX_QUIC_UNSET_PN; /* temporary socket */ + + ngx_memcpy(tmp->sid.id, pkt->dcid.data, pkt->dcid.len); + tmp->sid.len = pkt->dcid.len; + + if (ngx_quic_listen(c, qc, tmp) != NGX_OK) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + c->udp = NULL; + + return NGX_ERROR; +} + + +ngx_quic_socket_t * +ngx_quic_create_socket(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_queue_t *q; + ngx_quic_socket_t *sock; + + if (!ngx_queue_empty(&qc->free_sockets)) { + + q = ngx_queue_head(&qc->free_sockets); + sock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + ngx_queue_remove(&sock->queue); + + ngx_memzero(sock, sizeof(ngx_quic_socket_t)); + + } else { + + sock = ngx_pcalloc(c->pool, sizeof(ngx_quic_socket_t)); + if (sock == NULL) { + return NULL; + } + } + + sock->sid.len = NGX_QUIC_SERVER_CID_LEN; + if (ngx_quic_create_server_id(c, sock->sid.id) != NGX_OK) { + return NULL; + } + + sock->sid.seqnum = qc->server_seqnum++; + + return sock; +} + + +void +ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ngx_queue_remove(&qsock->queue); + ngx_queue_insert_head(&qc->free_sockets, &qsock->queue); + + ngx_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + qc->nsockets--; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket seq:%L closed nsock:%ui", + (int64_t) qsock->sid.seqnum, qc->nsockets); +} + + +ngx_int_t +ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_socket_t *qsock) +{ + ngx_str_t id; + ngx_quic_server_id_t *sid; + + sid = &qsock->sid; + + id.data = sid->id; + id.len = sid->len; + + qsock->udp.connection = c; + qsock->udp.node.key = ngx_crc32_long(id.data, id.len); + qsock->udp.key = id; + + ngx_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); + + ngx_queue_insert_tail(&qc->sockets, &qsock->queue); + + qc->nsockets++; + qsock->quic = qc; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic socket seq:%L listening at sid:%xV nsock:%ui", + (int64_t) sid->seqnum, &id, qc->nsockets); + + return NGX_OK; +} + + +void +ngx_quic_close_sockets(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + while (!ngx_queue_empty(&qc->sockets)) { + q = ngx_queue_head(&qc->sockets); + qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + ngx_quic_close_socket(c, qsock); + } +} + + +ngx_quic_socket_t * +ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum) +{ + ngx_queue_t *q; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + for (q = ngx_queue_head(&qc->sockets); + q != ngx_queue_sentinel(&qc->sockets); + q = ngx_queue_next(q)) + { + qsock = ngx_queue_data(q, ngx_quic_socket_t, queue); + + if (qsock->sid.seqnum == seqnum) { + return qsock; + } + } + + return NULL; +} diff --git a/src/event/quic/ngx_event_quic_socket.h b/src/event/quic/ngx_event_quic_socket.h new file mode 100644 index 0000000..68ecc06 --- /dev/null +++ b/src/event/quic/ngx_event_quic_socket.h @@ -0,0 +1,28 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ +#define _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +ngx_int_t ngx_quic_open_sockets(ngx_connection_t *c, + ngx_quic_connection_t *qc, ngx_quic_header_t *pkt); +void ngx_quic_close_sockets(ngx_connection_t *c); + +ngx_quic_socket_t *ngx_quic_create_socket(ngx_connection_t *c, + ngx_quic_connection_t *qc); +ngx_int_t ngx_quic_listen(ngx_connection_t *c, ngx_quic_connection_t *qc, + ngx_quic_socket_t *qsock); +void ngx_quic_close_socket(ngx_connection_t *c, ngx_quic_socket_t *qsock); + +ngx_quic_socket_t *ngx_quic_find_socket(ngx_connection_t *c, uint64_t seqnum); + + +#endif /* _NGX_EVENT_QUIC_SOCKET_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_ssl.c b/src/event/quic/ngx_event_quic_ssl.c new file mode 100644 index 0000000..ba0b592 --- /dev/null +++ b/src/event/quic/ngx_event_quic_ssl.c @@ -0,0 +1,595 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + + +#if defined OPENSSL_IS_BORINGSSL \ + || defined LIBRESSL_VERSION_NUMBER \ + || NGX_QUIC_OPENSSL_COMPAT +#define NGX_QUIC_BORINGSSL_API 1 +#endif + + +/* + * RFC 9000, 7.5. Cryptographic Message Buffering + * + * Implementations MUST support buffering at least 4096 bytes of data + */ +#define NGX_QUIC_MAX_BUFFERED 65535 + + +#if (NGX_QUIC_BORINGSSL_API) +static int ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +static int ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *secret, size_t secret_len); +#else +static int ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *read_secret, + const uint8_t *write_secret, size_t secret_len); +#endif + +static int ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len); +static int ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn); +static int ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, uint8_t alert); +static ngx_int_t ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, + enum ssl_encryption_level_t level); + + +#if (NGX_QUIC_BORINGSSL_API) + +static int +ngx_quic_set_read_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *rsecret, size_t secret_len) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_read_secret() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic read secret len:%uz %*xs", secret_len, + secret_len, rsecret); +#endif + + if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level, + cipher, rsecret, secret_len) + != NGX_OK) + { + return 0; + } + + return 1; +} + + +static int +ngx_quic_set_write_secret(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const SSL_CIPHER *cipher, + const uint8_t *wsecret, size_t secret_len) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_write_secret() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic write secret len:%uz %*xs", secret_len, + secret_len, wsecret); +#endif + + if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level, + cipher, wsecret, secret_len) + != NGX_OK) + { + return 0; + } + + return 1; +} + +#else + +static int +ngx_quic_set_encryption_secrets(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *rsecret, + const uint8_t *wsecret, size_t secret_len) +{ + ngx_connection_t *c; + const SSL_CIPHER *cipher; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_set_encryption_secrets() level:%d", level); +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic read secret len:%uz %*xs", secret_len, + secret_len, rsecret); +#endif + + cipher = SSL_get_current_cipher(ssl_conn); + + if (ngx_quic_keys_set_encryption_secret(c->log, 0, qc->keys, level, + cipher, rsecret, secret_len) + != NGX_OK) + { + return 0; + } + + if (level == ssl_encryption_early_data) { + return 1; + } + +#ifdef NGX_QUIC_DEBUG_CRYPTO + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic write secret len:%uz %*xs", secret_len, + secret_len, wsecret); +#endif + + if (ngx_quic_keys_set_encryption_secret(c->log, 1, qc->keys, level, + cipher, wsecret, secret_len) + != NGX_OK) + { + return 0; + } + + return 1; +} + +#endif + + +static int +ngx_quic_add_handshake_data(ngx_ssl_conn_t *ssl_conn, + enum ssl_encryption_level_t level, const uint8_t *data, size_t len) +{ + u_char *p, *end; + size_t client_params_len; + ngx_chain_t *out; + const uint8_t *client_params; + ngx_quic_tp_t ctp; + ngx_quic_frame_t *frame; + ngx_connection_t *c; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; +#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + unsigned int alpn_len; + const unsigned char *alpn_data; +#endif + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + qc = ngx_quic_get_connection(c); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_add_handshake_data"); + + if (!qc->client_tp_done) { + /* + * things to do once during handshake: check ALPN and transport + * parameters; we want to break handshake if something is wrong + * here; + */ + +#if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + + SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); + + if (alpn_len == 0) { + qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL); + qc->error_reason = "unsupported protocol in ALPN extension"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic unsupported protocol in ALPN extension"); + return 0; + } + +#endif + + SSL_get_peer_quic_transport_params(ssl_conn, &client_params, + &client_params_len); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic SSL_get_peer_quic_transport_params():" + " params_len:%ui", client_params_len); + + if (client_params_len == 0) { + /* RFC 9001, 8.2. QUIC Transport Parameters Extension */ + qc->error = NGX_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION); + qc->error_reason = "missing transport parameters"; + + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "missing transport parameters"); + return 0; + } + + p = (u_char *) client_params; + end = p + client_params_len; + + /* defaults for parameters not sent by client */ + ngx_memcpy(&ctp, &qc->ctp, sizeof(ngx_quic_tp_t)); + + if (ngx_quic_parse_transport_params(p, end, &ctp, c->log) + != NGX_OK) + { + qc->error = NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "failed to process transport parameters"; + + return 0; + } + + if (ngx_quic_apply_transport_params(c, &ctp) != NGX_OK) { + return 0; + } + + qc->client_tp_done = 1; + } + + ctx = ngx_quic_get_send_ctx(qc, level); + + out = ngx_quic_copy_buffer(c, (u_char *) data, len); + if (out == NGX_CHAIN_ERROR) { + return 0; + } + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return 0; + } + + frame->data = out; + frame->level = level; + frame->type = NGX_QUIC_FT_CRYPTO; + frame->u.crypto.offset = ctx->crypto_sent; + frame->u.crypto.length = len; + + ctx->crypto_sent += len; + + ngx_quic_queue_frame(qc, frame); + + return 1; +} + + +static int +ngx_quic_flush_flight(ngx_ssl_conn_t *ssl_conn) +{ +#if (NGX_DEBUG) + ngx_connection_t *c; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_flush_flight()"); +#endif + return 1; +} + + +static int +ngx_quic_send_alert(ngx_ssl_conn_t *ssl_conn, enum ssl_encryption_level_t level, + uint8_t alert) +{ + ngx_connection_t *c; + ngx_quic_connection_t *qc; + + c = ngx_ssl_get_connection((ngx_ssl_conn_t *) ssl_conn); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic ngx_quic_send_alert() level:%s alert:%d", + ngx_quic_level_name(level), (int) alert); + + /* already closed on regular shutdown */ + + qc = ngx_quic_get_connection(c); + if (qc == NULL) { + return 1; + } + + qc->error = NGX_QUIC_ERR_CRYPTO(alert); + qc->error_reason = "handshake failed"; + + return 1; +} + + +ngx_int_t +ngx_quic_handle_crypto_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + uint64_t last; + ngx_chain_t *cl; + ngx_quic_send_ctx_t *ctx; + ngx_quic_connection_t *qc; + ngx_quic_crypto_frame_t *f; + + qc = ngx_quic_get_connection(c); + + if (!ngx_quic_keys_available(qc->keys, pkt->level, 0)) { + return NGX_OK; + } + + ctx = ngx_quic_get_send_ctx(qc, pkt->level); + f = &frame->u.crypto; + + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; + + if (last > ctx->crypto.offset + NGX_QUIC_MAX_BUFFERED) { + qc->error = NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED; + return NGX_ERROR; + } + + if (last <= ctx->crypto.offset) { + if (pkt->level == ssl_encryption_initial) { + /* speeding up handshake completion */ + + if (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + + ctx = ngx_quic_get_send_ctx(qc, ssl_encryption_handshake); + while (!ngx_queue_empty(&ctx->sent)) { + ngx_quic_resend_frames(c, ctx); + } + } + } + + return NGX_OK; + } + + if (f->offset == ctx->crypto.offset) { + if (ngx_quic_crypto_input(c, frame->data, pkt->level) != NGX_OK) { + return NGX_ERROR; + } + + ngx_quic_skip_buffer(c, &ctx->crypto, last); + + } else { + if (ngx_quic_write_buffer(c, &ctx->crypto, frame->data, f->length, + f->offset) + == NGX_CHAIN_ERROR) + { + return NGX_ERROR; + } + } + + cl = ngx_quic_read_buffer(c, &ctx->crypto, (uint64_t) -1); + + if (cl) { + if (ngx_quic_crypto_input(c, cl, pkt->level) != NGX_OK) { + return NGX_ERROR; + } + + ngx_quic_free_chain(c, cl); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_crypto_input(ngx_connection_t *c, ngx_chain_t *data, + enum ssl_encryption_level_t level) +{ + int n, sslerr; + ngx_buf_t *b; + ngx_chain_t *cl; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + ssl_conn = c->ssl->connection; + + for (cl = data; cl; cl = cl->next) { + b = cl->buf; + + if (!SSL_provide_quic_data(ssl_conn, level, b->pos, b->last - b->pos)) { + ngx_ssl_error(NGX_LOG_INFO, c->log, 0, + "SSL_provide_quic_data() failed"); + return NGX_ERROR; + } + } + + n = SSL_do_handshake(ssl_conn); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n); + + if (n <= 0) { + sslerr = SSL_get_error(ssl_conn, n); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + + if (sslerr != SSL_ERROR_WANT_READ) { + + if (c->ssl->handshake_rejected) { + ngx_connection_error(c, 0, "handshake rejected"); + ERR_clear_error(); + + return NGX_ERROR; + } + + ngx_ssl_error(NGX_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + return NGX_ERROR; + } + } + + if (n <= 0 || SSL_in_init(ssl_conn)) { + if (ngx_quic_keys_available(qc->keys, ssl_encryption_early_data, 0) + && qc->client_tp_done) + { + if (ngx_quic_init_streams(c) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; + } + +#if (NGX_DEBUG) + ngx_ssl_handshake_log(c); +#endif + + c->ssl->handshaked = 1; + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_HANDSHAKE_DONE; + ngx_quic_queue_frame(qc, frame); + + if (qc->conf->retry) { + if (ngx_quic_send_new_token(c, qc->path) != NGX_OK) { + return NGX_ERROR; + } + } + + /* + * RFC 9001, 9.5. Header Protection Timing Side Channels + * + * Generating next keys before a key update is received. + */ + + ngx_post_event(&qc->key_update, &ngx_posted_events); + + /* + * RFC 9001, 4.9.2. Discarding Handshake Keys + * + * An endpoint MUST discard its Handshake keys + * when the TLS handshake is confirmed. + */ + ngx_quic_discard_ctx(c, ssl_encryption_handshake); + + ngx_quic_discover_path_mtu(c, qc->path); + + /* start accepting clients on negotiated number of server ids */ + if (ngx_quic_create_sockets(c) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_quic_init_streams(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_init_connection(ngx_connection_t *c) +{ + u_char *p; + size_t clen; + ssize_t len; + ngx_str_t dcid; + ngx_ssl_conn_t *ssl_conn; + ngx_quic_socket_t *qsock; + ngx_quic_connection_t *qc; + static SSL_QUIC_METHOD quic_method; + + qc = ngx_quic_get_connection(c); + + if (ngx_ssl_create_connection(qc->conf->ssl, c, 0) != NGX_OK) { + return NGX_ERROR; + } + + c->ssl->no_wait_shutdown = 1; + + ssl_conn = c->ssl->connection; + + if (!quic_method.send_alert) { +#if (NGX_QUIC_BORINGSSL_API) + quic_method.set_read_secret = ngx_quic_set_read_secret; + quic_method.set_write_secret = ngx_quic_set_write_secret; +#else + quic_method.set_encryption_secrets = ngx_quic_set_encryption_secrets; +#endif + quic_method.add_handshake_data = ngx_quic_add_handshake_data; + quic_method.flush_flight = ngx_quic_flush_flight; + quic_method.send_alert = ngx_quic_send_alert; + } + + if (SSL_set_quic_method(ssl_conn, &quic_method) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_method() failed"); + return NGX_ERROR; + } + +#ifdef OPENSSL_INFO_QUIC + if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { + SSL_set_quic_early_data_enabled(ssl_conn, 1); + } +#endif + + qsock = ngx_quic_get_socket(c); + + dcid.data = qsock->sid.id; + dcid.len = qsock->sid.len; + + if (ngx_quic_new_sr_token(c, &dcid, qc->conf->sr_token_key, qc->tp.sr_token) + != NGX_OK) + { + return NGX_ERROR; + } + + len = ngx_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); + /* always succeeds */ + + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + len = ngx_quic_create_transport_params(p, p + len, &qc->tp, NULL); + if (len < 0) { + return NGX_ERROR; + } + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic transport parameters len:%uz %*xs", len, len, p); +#endif + + if (SSL_set_quic_transport_params(ssl_conn, p, len) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_transport_params() failed"); + return NGX_ERROR; + } + +#ifdef OPENSSL_IS_BORINGSSL + if (SSL_set_quic_early_data_context(ssl_conn, p, clen) == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "quic SSL_set_quic_early_data_context() failed"); + return NGX_ERROR; + } +#endif + + return NGX_OK; +} diff --git a/src/event/quic/ngx_event_quic_ssl.h b/src/event/quic/ngx_event_quic_ssl.h new file mode 100644 index 0000000..ee0aa07 --- /dev/null +++ b/src/event/quic/ngx_event_quic_ssl.h @@ -0,0 +1,19 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_SSL_H_INCLUDED_ +#define _NGX_EVENT_QUIC_SSL_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + +ngx_int_t ngx_quic_init_connection(ngx_connection_t *c); + +ngx_int_t ngx_quic_handle_crypto_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); + +#endif /* _NGX_EVENT_QUIC_SSL_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_streams.c b/src/event/quic/ngx_event_quic_streams.c new file mode 100644 index 0000000..a9a21f5 --- /dev/null +++ b/src/event/quic/ngx_event_quic_streams.c @@ -0,0 +1,1828 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + + +#define NGX_QUIC_STREAM_GONE (void *) -1 + + +static ngx_int_t ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, + ngx_uint_t err); +static ngx_int_t ngx_quic_shutdown_stream_send(ngx_connection_t *c); +static ngx_int_t ngx_quic_shutdown_stream_recv(ngx_connection_t *c); +static ngx_quic_stream_t *ngx_quic_get_stream(ngx_connection_t *c, uint64_t id); +static ngx_int_t ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id); +static void ngx_quic_init_stream_handler(ngx_event_t *ev); +static void ngx_quic_init_streams_handler(ngx_connection_t *c); +static ngx_int_t ngx_quic_do_init_streams(ngx_connection_t *c); +static ngx_quic_stream_t *ngx_quic_create_stream(ngx_connection_t *c, + uint64_t id); +static void ngx_quic_empty_handler(ngx_event_t *ev); +static ssize_t ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, + size_t size); +static ssize_t ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, + size_t size); +static ngx_chain_t *ngx_quic_stream_send_chain(ngx_connection_t *c, + ngx_chain_t *in, off_t limit); +static ngx_int_t ngx_quic_stream_flush(ngx_quic_stream_t *qs); +static void ngx_quic_stream_cleanup_handler(void *data); +static ngx_int_t ngx_quic_close_stream(ngx_quic_stream_t *qs); +static ngx_int_t ngx_quic_can_shutdown(ngx_connection_t *c); +static ngx_int_t ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last); +static ngx_int_t ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last); +static ngx_int_t ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs); +static ngx_int_t ngx_quic_update_max_data(ngx_connection_t *c); +static void ngx_quic_set_event(ngx_event_t *ev); + + +ngx_connection_t * +ngx_quic_open_stream(ngx_connection_t *c, ngx_uint_t bidi) +{ + uint64_t id; + ngx_connection_t *pc, *sc; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + pc = c->quic ? c->quic->parent : c; + qc = ngx_quic_get_connection(pc); + + if (qc->closing) { + return NULL; + } + + if (bidi) { + if (qc->streams.server_streams_bidi + >= qc->streams.server_max_streams_bidi) + { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic too many server bidi streams:%uL", + qc->streams.server_streams_bidi); + return NULL; + } + + id = (qc->streams.server_streams_bidi << 2) + | NGX_QUIC_STREAM_SERVER_INITIATED; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic creating server bidi stream" + " streams:%uL max:%uL id:0x%xL", + qc->streams.server_streams_bidi, + qc->streams.server_max_streams_bidi, id); + + qc->streams.server_streams_bidi++; + + } else { + if (qc->streams.server_streams_uni + >= qc->streams.server_max_streams_uni) + { + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic too many server uni streams:%uL", + qc->streams.server_streams_uni); + return NULL; + } + + id = (qc->streams.server_streams_uni << 2) + | NGX_QUIC_STREAM_SERVER_INITIATED + | NGX_QUIC_STREAM_UNIDIRECTIONAL; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic creating server uni stream" + " streams:%uL max:%uL id:0x%xL", + qc->streams.server_streams_uni, + qc->streams.server_max_streams_uni, id); + + qc->streams.server_streams_uni++; + } + + qs = ngx_quic_create_stream(pc, id); + if (qs == NULL) { + return NULL; + } + + sc = qs->connection; + + sc->write->active = 1; + sc->write->ready = 1; + + if (bidi) { + sc->read->active = 1; + } + + return sc; +} + + +void +ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel) +{ + ngx_rbtree_node_t **p; + ngx_quic_stream_t *qn, *qnt; + + for ( ;; ) { + qn = (ngx_quic_stream_t *) node; + qnt = (ngx_quic_stream_t *) temp; + + p = (qn->id < qnt->id) ? &temp->left : &temp->right; + + if (*p == sentinel) { + break; + } + + temp = *p; + } + + *p = node; + node->parent = temp; + node->left = sentinel; + node->right = sentinel; + ngx_rbt_red(node); +} + + +ngx_quic_stream_t * +ngx_quic_find_stream(ngx_rbtree_t *rbtree, uint64_t id) +{ + ngx_rbtree_node_t *node, *sentinel; + ngx_quic_stream_t *qn; + + node = rbtree->root; + sentinel = rbtree->sentinel; + + while (node != sentinel) { + qn = (ngx_quic_stream_t *) node; + + if (id == qn->id) { + return qn; + } + + node = (id < qn->id) ? node->left : node->right; + } + + return NULL; +} + + +ngx_int_t +ngx_quic_close_streams(ngx_connection_t *c, ngx_quic_connection_t *qc) +{ + ngx_pool_t *pool; + ngx_queue_t *q, posted_events; + ngx_rbtree_t *tree; + ngx_connection_t *sc; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + + while (!ngx_queue_empty(&qc->streams.uninitialized)) { + q = ngx_queue_head(&qc->streams.uninitialized); + ngx_queue_remove(q); + + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); + pool = qs->connection->pool; + + ngx_close_connection(qs->connection); + ngx_destroy_pool(pool); + } + + tree = &qc->streams.tree; + + if (tree->root == tree->sentinel) { + return NGX_OK; + } + + ngx_queue_init(&posted_events); + + node = ngx_rbtree_min(tree->root, tree->sentinel); + + while (node) { + qs = (ngx_quic_stream_t *) node; + node = ngx_rbtree_next(tree, node); + sc = qs->connection; + + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; + qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; + + if (sc == NULL) { + ngx_quic_close_stream(qs); + continue; + } + + sc->read->error = 1; + sc->read->ready = 1; + sc->write->error = 1; + sc->write->ready = 1; + + sc->close = 1; + + if (sc->read->posted) { + ngx_delete_posted_event(sc->read); + } + + ngx_post_event(sc->read, &posted_events); + } + + ngx_event_process_posted((ngx_cycle_t *) ngx_cycle, &posted_events); + + if (tree->root == tree->sentinel) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic connection has active streams"); + + return NGX_AGAIN; +} + + +ngx_int_t +ngx_quic_reset_stream(ngx_connection_t *c, ngx_uint_t err) +{ + return ngx_quic_do_reset_stream(c->quic, err); +} + + +static ngx_int_t +ngx_quic_do_reset_stream(ngx_quic_stream_t *qs, ngx_uint_t err) +{ + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_RECVD + || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_SENT + || qs->send_state == NGX_QUIC_STREAM_SEND_RESET_RECVD) + { + return NGX_OK; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_RESET_SENT; + qs->send_final_size = qs->send_offset; + + if (qs->connection) { + qs->connection->write->error = 1; + } + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL reset", qs->id); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RESET_STREAM; + frame->u.reset_stream.id = qs->id; + frame->u.reset_stream.error_code = err; + frame->u.reset_stream.final_size = qs->send_offset; + + ngx_quic_queue_frame(qc, frame); + + ngx_quic_free_buffer(pc, &qs->send); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_shutdown_stream(ngx_connection_t *c, int how) +{ + if (how == NGX_RDWR_SHUTDOWN || how == NGX_WRITE_SHUTDOWN) { + if (ngx_quic_shutdown_stream_send(c) != NGX_OK) { + return NGX_ERROR; + } + } + + if (how == NGX_RDWR_SHUTDOWN || how == NGX_READ_SHUTDOWN) { + if (ngx_quic_shutdown_stream_recv(c) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_shutdown_stream_send(ngx_connection_t *c) +{ + ngx_quic_stream_t *qs; + + qs = c->quic; + + if (qs->send_state != NGX_QUIC_STREAM_SEND_READY + && qs->send_state != NGX_QUIC_STREAM_SEND_SEND) + { + return NGX_OK; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_SEND; + qs->send_final_size = c->sent; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0, + "quic stream id:0x%xL send shutdown", qs->id); + + return ngx_quic_stream_flush(qs); +} + + +static ngx_int_t +ngx_quic_shutdown_stream_recv(ngx_connection_t *c) +{ + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + + if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV + && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN) + { + return NGX_OK; + } + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (qc->conf->stream_close_code == 0) { + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL recv shutdown", qs->id); + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STOP_SENDING; + frame->u.stop_sending.id = qs->id; + frame->u.stop_sending.error_code = qc->conf->stream_close_code; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static ngx_quic_stream_t * +ngx_quic_get_stream(ngx_connection_t *c, uint64_t id) +{ + uint64_t min_id; + ngx_event_t *rev; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + qs = ngx_quic_find_stream(&qc->streams.tree, id); + + if (qs) { + return qs; + } + + if (qc->shutdown || qc->closing) { + return NGX_QUIC_STREAM_GONE; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL is missing", id); + + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if ((id >> 2) < qc->streams.server_streams_uni) { + return NGX_QUIC_STREAM_GONE; + } + + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + if ((id >> 2) < qc->streams.client_streams_uni) { + return NGX_QUIC_STREAM_GONE; + } + + if ((id >> 2) >= qc->streams.client_max_streams_uni) { + qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; + return NULL; + } + + min_id = (qc->streams.client_streams_uni << 2) + | NGX_QUIC_STREAM_UNIDIRECTIONAL; + qc->streams.client_streams_uni = (id >> 2) + 1; + + } else { + + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + if ((id >> 2) < qc->streams.server_streams_bidi) { + return NGX_QUIC_STREAM_GONE; + } + + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NULL; + } + + if ((id >> 2) < qc->streams.client_streams_bidi) { + return NGX_QUIC_STREAM_GONE; + } + + if ((id >> 2) >= qc->streams.client_max_streams_bidi) { + qc->error = NGX_QUIC_ERR_STREAM_LIMIT_ERROR; + return NULL; + } + + min_id = (qc->streams.client_streams_bidi << 2); + qc->streams.client_streams_bidi = (id >> 2) + 1; + } + + /* + * RFC 9000, 2.1. Stream Types and Identifiers + * + * successive streams of each type are created with numerically increasing + * stream IDs. A stream ID that is used out of order results in all + * streams of that type with lower-numbered stream IDs also being opened. + */ + +#if (NGX_SUPPRESS_WARN) + qs = NULL; +#endif + + for ( /* void */ ; min_id <= id; min_id += 0x04) { + + qs = ngx_quic_create_stream(c, min_id); + + if (qs == NULL) { + if (ngx_quic_reject_stream(c, min_id) != NGX_OK) { + return NULL; + } + + continue; + } + + ngx_queue_insert_tail(&qc->streams.uninitialized, &qs->queue); + + rev = qs->connection->read; + rev->handler = ngx_quic_init_stream_handler; + + if (qc->streams.initialized) { + ngx_post_event(rev, &ngx_posted_events); + + if (qc->push.posted) { + /* + * The posted stream can produce output immediately. + * By postponing the push event, we coalesce the stream + * output with queued frames in one UDP datagram. + */ + + ngx_delete_posted_event(&qc->push); + ngx_post_event(&qc->push, &ngx_posted_events); + } + } + } + + if (qs == NULL) { + return NGX_QUIC_STREAM_GONE; + } + + return qs; +} + + +static ngx_int_t +ngx_quic_reject_stream(ngx_connection_t *c, uint64_t id) +{ + uint64_t code; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + code = (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + ? qc->conf->stream_reject_code_uni + : qc->conf->stream_reject_code_bidi; + + if (code == 0) { + return NGX_DECLINED; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL reject err:0x%xL", id, code); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_RESET_STREAM; + frame->u.reset_stream.id = id; + frame->u.reset_stream.error_code = code; + frame->u.reset_stream.final_size = 0; + + ngx_quic_queue_frame(qc, frame); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STOP_SENDING; + frame->u.stop_sending.id = id; + frame->u.stop_sending.error_code = code; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static void +ngx_quic_init_stream_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_quic_stream_t *qs; + + c = ev->data; + qs = c->quic; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init stream"); + + if ((qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { + c->write->active = 1; + c->write->ready = 1; + } + + c->read->active = 1; + + ngx_queue_remove(&qs->queue); + + c->listening->handler(c); +} + + +ngx_int_t +ngx_quic_init_streams(ngx_connection_t *c) +{ + ngx_int_t rc; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (qc->streams.initialized) { + return NGX_OK; + } + + rc = ngx_ssl_ocsp_validate(c); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_AGAIN) { + c->ssl->handler = ngx_quic_init_streams_handler; + return NGX_OK; + } + + return ngx_quic_do_init_streams(c); +} + + +static void +ngx_quic_init_streams_handler(ngx_connection_t *c) +{ + if (ngx_quic_do_init_streams(c) != NGX_OK) { + ngx_quic_close_connection(c, NGX_ERROR); + } +} + + +static ngx_int_t +ngx_quic_do_init_streams(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0, "quic init streams"); + + qc = ngx_quic_get_connection(c); + + if (qc->conf->init) { + if (qc->conf->init(c) != NGX_OK) { + return NGX_ERROR; + } + } + + for (q = ngx_queue_head(&qc->streams.uninitialized); + q != ngx_queue_sentinel(&qc->streams.uninitialized); + q = ngx_queue_next(q)) + { + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); + ngx_post_event(qs->connection->read, &ngx_posted_events); + } + + qc->streams.initialized = 1; + + if (!qc->closing && qc->close.timer_set) { + ngx_del_timer(&qc->close); + } + + return NGX_OK; +} + + +static ngx_quic_stream_t * +ngx_quic_create_stream(ngx_connection_t *c, uint64_t id) +{ + ngx_str_t addr_text; + ngx_log_t *log; + ngx_pool_t *pool; + ngx_uint_t reusable; + ngx_queue_t *q; + struct sockaddr *sockaddr; + ngx_connection_t *sc; + ngx_quic_stream_t *qs; + ngx_pool_cleanup_t *cln; + ngx_quic_connection_t *qc; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL create", id); + + qc = ngx_quic_get_connection(c); + + if (!ngx_queue_empty(&qc->streams.free)) { + q = ngx_queue_head(&qc->streams.free); + qs = ngx_queue_data(q, ngx_quic_stream_t, queue); + ngx_queue_remove(&qs->queue); + + } else { + /* + * the number of streams is limited by transport + * parameters and application requirements + */ + + qs = ngx_palloc(c->pool, sizeof(ngx_quic_stream_t)); + if (qs == NULL) { + return NULL; + } + } + + ngx_memzero(qs, sizeof(ngx_quic_stream_t)); + + qs->node.key = id; + qs->parent = c; + qs->id = id; + qs->send_final_size = (uint64_t) -1; + qs->recv_final_size = (uint64_t) -1; + + pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, c->log); + if (pool == NULL) { + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + log = ngx_palloc(pool, sizeof(ngx_log_t)); + if (log == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + *log = *c->log; + pool->log = log; + + sockaddr = ngx_palloc(pool, c->socklen); + if (sockaddr == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + ngx_memcpy(sockaddr, c->sockaddr, c->socklen); + + if (c->addr_text.data) { + addr_text.data = ngx_pnalloc(pool, c->addr_text.len); + if (addr_text.data == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + return NULL; + } + + ngx_memcpy(addr_text.data, c->addr_text.data, c->addr_text.len); + addr_text.len = c->addr_text.len; + + } else { + addr_text.len = 0; + addr_text.data = NULL; + } + + reusable = c->reusable; + ngx_reusable_connection(c, 0); + + sc = ngx_get_connection(c->fd, log); + if (sc == NULL) { + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + ngx_reusable_connection(c, reusable); + return NULL; + } + + qs->connection = sc; + + sc->quic = qs; + sc->shared = 1; + sc->type = SOCK_STREAM; + sc->pool = pool; + sc->ssl = c->ssl; + sc->sockaddr = sockaddr; + sc->socklen = c->socklen; + sc->listening = c->listening; + sc->addr_text = addr_text; + sc->local_sockaddr = c->local_sockaddr; + sc->local_socklen = c->local_socklen; + sc->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + sc->start_time = c->start_time; + sc->tcp_nodelay = NGX_TCP_NODELAY_DISABLED; + + sc->recv = ngx_quic_stream_recv; + sc->send = ngx_quic_stream_send; + sc->send_chain = ngx_quic_stream_send_chain; + + sc->read->log = log; + sc->write->log = log; + + sc->read->handler = ngx_quic_empty_handler; + sc->write->handler = ngx_quic_empty_handler; + + log->connection = sc->number; + + if (id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + qs->send_max_data = qc->ctp.initial_max_stream_data_uni; + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; + qs->send_state = NGX_QUIC_STREAM_SEND_READY; + + } else { + qs->recv_max_data = qc->tp.initial_max_stream_data_uni; + qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD; + } + + } else { + if (id & NGX_QUIC_STREAM_SERVER_INITIATED) { + qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; + qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_local; + + } else { + qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_local; + qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_remote; + } + + qs->recv_state = NGX_QUIC_STREAM_RECV_RECV; + qs->send_state = NGX_QUIC_STREAM_SEND_READY; + } + + qs->recv_window = qs->recv_max_data; + + cln = ngx_pool_cleanup_add(pool, 0); + if (cln == NULL) { + ngx_close_connection(sc); + ngx_destroy_pool(pool); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + ngx_reusable_connection(c, reusable); + return NULL; + } + + cln->handler = ngx_quic_stream_cleanup_handler; + cln->data = sc; + + ngx_rbtree_insert(&qc->streams.tree, &qs->node); + + return qs; +} + + +void +ngx_quic_cancelable_stream(ngx_connection_t *c) +{ + ngx_connection_t *pc; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (!qs->cancelable) { + qs->cancelable = 1; + + if (ngx_quic_can_shutdown(pc) == NGX_OK) { + ngx_reusable_connection(pc, 1); + + if (qc->shutdown) { + ngx_quic_shutdown_quic(pc); + } + } + } +} + + +static void +ngx_quic_empty_handler(ngx_event_t *ev) +{ +} + + +static ssize_t +ngx_quic_stream_recv(ngx_connection_t *c, u_char *buf, size_t size) +{ + ssize_t len; + ngx_buf_t *b; + ngx_chain_t *cl, *in; + ngx_event_t *rev; + ngx_connection_t *pc; + ngx_quic_stream_t *qs; + + qs = c->quic; + pc = qs->parent; + rev = c->read; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD + || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_READ; + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL recv buf:%uz", qs->id, size); + + if (size == 0) { + return 0; + } + + in = ngx_quic_read_buffer(pc, &qs->recv, size); + if (in == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + len = 0; + + for (cl = in; cl; cl = cl->next) { + b = cl->buf; + len += b->last - b->pos; + buf = ngx_cpymem(buf, b->pos, b->last - b->pos); + } + + ngx_quic_free_chain(pc, in); + + if (len == 0) { + rev->ready = 0; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_RECVD + && qs->recv_offset == qs->recv_final_size) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_READ; + } + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_DATA_READ) { + rev->eof = 1; + return 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv() not ready", qs->id); + return NGX_AGAIN; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv len:%z", qs->id, len); + + if (ngx_quic_update_flow(qs, qs->recv_offset + len) != NGX_OK) { + return NGX_ERROR; + } + + return len; +} + + +static ssize_t +ngx_quic_stream_send(ngx_connection_t *c, u_char *buf, size_t size) +{ + ngx_buf_t b; + ngx_chain_t cl; + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + b.memory = 1; + b.pos = buf; + b.last = buf + size; + + cl.buf = &b; + cl.next = NULL; + + if (ngx_quic_stream_send_chain(c, &cl, 0) == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + if (b.pos == buf) { + return NGX_AGAIN; + } + + return b.pos - buf; +} + + +static ngx_chain_t * +ngx_quic_stream_send_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + uint64_t n, flow; + ngx_event_t *wev; + ngx_connection_t *pc; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + wev = c->write; + + if (qs->send_state != NGX_QUIC_STREAM_SEND_READY + && qs->send_state != NGX_QUIC_STREAM_SEND_SEND) + { + wev->error = 1; + return NGX_CHAIN_ERROR; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_SEND; + + flow = qs->acked + qc->conf->stream_buffer_size - qs->sent; + + if (flow == 0) { + wev->ready = 0; + return in; + } + + if (limit == 0 || limit > (off_t) flow) { + limit = flow; + } + + n = qs->send.size; + + in = ngx_quic_write_buffer(pc, &qs->send, in, limit, qs->sent); + if (in == NGX_CHAIN_ERROR) { + return NGX_CHAIN_ERROR; + } + + n = qs->send.size - n; + c->sent += n; + qs->sent += n; + qc->streams.sent += n; + + if (flow == n) { + wev->ready = 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic send_chain sent:%uL", n); + + if (ngx_quic_stream_flush(qs) != NGX_OK) { + return NGX_CHAIN_ERROR; + } + + return in; +} + + +static ngx_int_t +ngx_quic_stream_flush(ngx_quic_stream_t *qs) +{ + off_t limit, len; + ngx_uint_t last; + ngx_chain_t *out; + ngx_quic_frame_t *frame; + ngx_connection_t *pc; + ngx_quic_connection_t *qc; + + if (qs->send_state != NGX_QUIC_STREAM_SEND_SEND) { + return NGX_OK; + } + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (qc->streams.send_max_data == 0) { + qc->streams.send_max_data = qc->ctp.initial_max_data; + } + + limit = ngx_min(qc->streams.send_max_data - qc->streams.send_offset, + qs->send_max_data - qs->send_offset); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flush limit:%O", qs->id, limit); + + len = qs->send.offset; + + out = ngx_quic_read_buffer(pc, &qs->send, limit); + if (out == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + len = qs->send.offset - len; + last = 0; + + if (qs->send_final_size != (uint64_t) -1 + && qs->send_final_size == qs->send.offset) + { + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_SENT; + last = 1; + } + + if (len == 0 && !last) { + return NGX_OK; + } + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_STREAM; + frame->data = out; + + frame->u.stream.off = 1; + frame->u.stream.len = 1; + frame->u.stream.fin = last; + + frame->u.stream.stream_id = qs->id; + frame->u.stream.offset = qs->send_offset; + frame->u.stream.length = len; + + ngx_quic_queue_frame(qc, frame); + + qs->send_offset += len; + qc->streams.send_offset += len; + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flush len:%O last:%ui", + qs->id, len, last); + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + return NGX_OK; +} + + +static void +ngx_quic_stream_cleanup_handler(void *data) +{ + ngx_connection_t *c = data; + + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qs = c->quic; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, qs->parent->log, 0, + "quic stream id:0x%xL cleanup", qs->id); + + if (ngx_quic_shutdown_stream(c, NGX_RDWR_SHUTDOWN) != NGX_OK) { + qs->connection = NULL; + goto failed; + } + + qs->connection = NULL; + + if (ngx_quic_close_stream(qs) != NGX_OK) { + goto failed; + } + + return; + +failed: + + qc = ngx_quic_get_connection(qs->parent); + qc->error = NGX_QUIC_ERR_INTERNAL_ERROR; + + ngx_post_event(&qc->close, &ngx_posted_events); +} + + +static ngx_int_t +ngx_quic_close_stream(ngx_quic_stream_t *qs) +{ + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (!qc->closing) { + /* make sure everything is sent and final size is received */ + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV) { + return NGX_OK; + } + + if (qs->send_state != NGX_QUIC_STREAM_SEND_DATA_RECVD + && qs->send_state != NGX_QUIC_STREAM_SEND_RESET_RECVD) + { + return NGX_OK; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL close", qs->id); + + ngx_quic_free_buffer(pc, &qs->send); + ngx_quic_free_buffer(pc, &qs->recv); + + ngx_rbtree_delete(&qc->streams.tree, &qs->node); + ngx_queue_insert_tail(&qc->streams.free, &qs->queue); + + if (qc->closing) { + /* schedule handler call to continue ngx_quic_close_connection() */ + ngx_post_event(&qc->close, &ngx_posted_events); + return NGX_OK; + } + + if (!pc->reusable && ngx_quic_can_shutdown(pc) == NGX_OK) { + ngx_reusable_connection(pc, 1); + } + + if (qc->shutdown) { + ngx_quic_shutdown_quic(pc); + return NGX_OK; + } + + if ((qs->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) { + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAMS; + + if (qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + frame->u.max_streams.limit = ++qc->streams.client_max_streams_uni; + frame->u.max_streams.bidi = 0; + + } else { + frame->u.max_streams.limit = ++qc->streams.client_max_streams_bidi; + frame->u.max_streams.bidi = 1; + } + + ngx_quic_queue_frame(qc, frame); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_can_shutdown(ngx_connection_t *c) +{ + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + tree = &qc->streams.tree; + + if (tree->root != tree->sentinel) { + for (node = ngx_rbtree_min(tree->root, tree->sentinel); + node; + node = ngx_rbtree_next(tree, node)) + { + qs = (ngx_quic_stream_t *) node; + + if (!qs->cancelable) { + return NGX_DECLINED; + } + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_stream_frame(ngx_connection_t *c, ngx_quic_header_t *pkt, + ngx_quic_frame_t *frame) +{ + uint64_t last; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + ngx_quic_stream_frame_t *f; + + qc = ngx_quic_get_connection(c); + f = &frame->u.stream; + + if ((f->stream_id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->stream_id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + /* no overflow since both values are 62-bit */ + last = f->offset + f->length; + + qs = ngx_quic_get_stream(c, f->stream_id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV + && qs->recv_state != NGX_QUIC_STREAM_RECV_SIZE_KNOWN) + { + return NGX_OK; + } + + if (ngx_quic_control_flow(qs, last) != NGX_OK) { + return NGX_ERROR; + } + + if (qs->recv_final_size != (uint64_t) -1 && last > qs->recv_final_size) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + if (last < qs->recv_offset) { + return NGX_OK; + } + + if (f->fin) { + if (qs->recv_final_size != (uint64_t) -1 && qs->recv_final_size != last) + { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + if (qs->recv_last > last) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + qs->recv_final_size = last; + qs->recv_state = NGX_QUIC_STREAM_RECV_SIZE_KNOWN; + } + + if (ngx_quic_write_buffer(c, &qs->recv, frame->data, f->length, f->offset) + == NGX_CHAIN_ERROR) + { + return NGX_ERROR; + } + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_SIZE_KNOWN + && qs->recv.size == qs->recv_final_size) + { + qs->recv_state = NGX_QUIC_STREAM_RECV_DATA_RECVD; + } + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + if (f->offset <= qs->recv_offset) { + ngx_quic_set_event(qs->connection->read); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_max_data_frame(ngx_connection_t *c, + ngx_quic_max_data_frame_t *f) +{ + ngx_rbtree_t *tree; + ngx_rbtree_node_t *node; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + tree = &qc->streams.tree; + + if (f->max_data <= qc->streams.send_max_data) { + return NGX_OK; + } + + if (tree->root == tree->sentinel + || qc->streams.send_offset < qc->streams.send_max_data) + { + /* not blocked on MAX_DATA */ + qc->streams.send_max_data = f->max_data; + return NGX_OK; + } + + qc->streams.send_max_data = f->max_data; + node = ngx_rbtree_min(tree->root, tree->sentinel); + + while (node && qc->streams.send_offset < qc->streams.send_max_data) { + + qs = (ngx_quic_stream_t *) node; + node = ngx_rbtree_next(tree, node); + + if (ngx_quic_stream_flush(qs) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f) +{ + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f) +{ + return ngx_quic_update_max_data(c); +} + + +ngx_int_t +ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f) +{ + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + qs = ngx_quic_get_stream(c, f->id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + return ngx_quic_update_max_stream_data(qs); +} + + +ngx_int_t +ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f) +{ + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + qs = ngx_quic_get_stream(c, f->id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (f->limit <= qs->send_max_data) { + return NGX_OK; + } + + if (qs->send_offset < qs->send_max_data) { + /* not blocked on MAX_STREAM_DATA */ + qs->send_max_data = f->limit; + return NGX_OK; + } + + qs->send_max_data = f->limit; + + return ngx_quic_stream_flush(qs); +} + + +ngx_int_t +ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f) +{ + ngx_event_t *rev; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED)) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + qs = ngx_quic_get_stream(c, f->id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_RECVD + || qs->recv_state == NGX_QUIC_STREAM_RECV_RESET_READ) + { + return NGX_OK; + } + + qs->recv_state = NGX_QUIC_STREAM_RECV_RESET_RECVD; + + if (ngx_quic_control_flow(qs, f->final_size) != NGX_OK) { + return NGX_ERROR; + } + + if (qs->recv_final_size != (uint64_t) -1 + && qs->recv_final_size != f->final_size) + { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + if (qs->recv_last > f->final_size) { + qc->error = NGX_QUIC_ERR_FINAL_SIZE_ERROR; + return NGX_ERROR; + } + + qs->recv_final_size = f->final_size; + + if (ngx_quic_update_flow(qs, qs->recv_final_size) != NGX_OK) { + return NGX_ERROR; + } + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + rev = qs->connection->read; + rev->error = 1; + + ngx_quic_set_event(rev); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f) +{ + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if ((f->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) + && (f->id & NGX_QUIC_STREAM_SERVER_INITIATED) == 0) + { + qc->error = NGX_QUIC_ERR_STREAM_STATE_ERROR; + return NGX_ERROR; + } + + qs = ngx_quic_get_stream(c, f->id); + + if (qs == NULL) { + return NGX_ERROR; + } + + if (qs == NGX_QUIC_STREAM_GONE) { + return NGX_OK; + } + + if (ngx_quic_do_reset_stream(qs, f->error_code) != NGX_OK) { + return NGX_ERROR; + } + + if (qs->connection == NULL) { + return ngx_quic_close_stream(qs); + } + + ngx_quic_set_event(qs->connection->write); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_handle_max_streams_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f) +{ + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + if (f->bidi) { + if (qc->streams.server_max_streams_bidi < f->limit) { + qc->streams.server_max_streams_bidi = f->limit; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic max_streams_bidi:%uL", f->limit); + } + + } else { + if (qc->streams.server_max_streams_uni < f->limit) { + qc->streams.server_max_streams_uni = f->limit; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic max_streams_uni:%uL", f->limit); + } + } + + return NGX_OK; +} + + +void +ngx_quic_handle_stream_ack(ngx_connection_t *c, ngx_quic_frame_t *f) +{ + uint64_t acked; + ngx_quic_stream_t *qs; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + switch (f->type) { + + case NGX_QUIC_FT_RESET_STREAM: + + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.reset_stream.id); + if (qs == NULL) { + return; + } + + qs->send_state = NGX_QUIC_STREAM_SEND_RESET_RECVD; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL ack reset final_size:%uL", + qs->id, f->u.reset_stream.final_size); + + break; + + case NGX_QUIC_FT_STREAM: + + qs = ngx_quic_find_stream(&qc->streams.tree, f->u.stream.stream_id); + if (qs == NULL) { + return; + } + + acked = qs->acked; + qs->acked += f->u.stream.length; + + if (f->u.stream.fin) { + qs->fin_acked = 1; + } + + if (qs->send_state == NGX_QUIC_STREAM_SEND_DATA_SENT + && qs->acked == qs->sent && qs->fin_acked) + { + qs->send_state = NGX_QUIC_STREAM_SEND_DATA_RECVD; + } + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL ack len:%uL fin:%d unacked:%uL", + qs->id, f->u.stream.length, f->u.stream.fin, + qs->sent - qs->acked); + + if (qs->connection + && qs->sent - acked == qc->conf->stream_buffer_size + && f->u.stream.length > 0) + { + ngx_quic_set_event(qs->connection->write); + } + + break; + + default: + return; + } + + if (qs->connection == NULL) { + ngx_quic_close_stream(qs); + } +} + + +static ngx_int_t +ngx_quic_control_flow(ngx_quic_stream_t *qs, uint64_t last) +{ + uint64_t len; + ngx_connection_t *pc; + ngx_quic_connection_t *qc; + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (last <= qs->recv_last) { + return NGX_OK; + } + + len = last - qs->recv_last; + + ngx_log_debug5(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flow control msd:%uL/%uL md:%uL/%uL", + qs->id, last, qs->recv_max_data, qc->streams.recv_last + len, + qc->streams.recv_max_data); + + qs->recv_last += len; + + if (qs->recv_state == NGX_QUIC_STREAM_RECV_RECV + && qs->recv_last > qs->recv_max_data) + { + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + return NGX_ERROR; + } + + qc->streams.recv_last += len; + + if (qc->streams.recv_last > qc->streams.recv_max_data) { + qc->error = NGX_QUIC_ERR_FLOW_CONTROL_ERROR; + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_update_flow(ngx_quic_stream_t *qs, uint64_t last) +{ + uint64_t len; + ngx_connection_t *pc; + ngx_quic_connection_t *qc; + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (last <= qs->recv_offset) { + return NGX_OK; + } + + len = last - qs->recv_offset; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flow update %uL", qs->id, last); + + qs->recv_offset += len; + + if (qs->recv_max_data <= qs->recv_offset + qs->recv_window / 2) { + if (ngx_quic_update_max_stream_data(qs) != NGX_OK) { + return NGX_ERROR; + } + } + + qc->streams.recv_offset += len; + + if (qc->streams.recv_max_data + <= qc->streams.recv_offset + qc->streams.recv_window / 2) + { + if (ngx_quic_update_max_data(pc) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_update_max_stream_data(ngx_quic_stream_t *qs) +{ + uint64_t recv_max_data; + ngx_connection_t *pc; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + pc = qs->parent; + qc = ngx_quic_get_connection(pc); + + if (qs->recv_state != NGX_QUIC_STREAM_RECV_RECV) { + return NGX_OK; + } + + recv_max_data = qs->recv_offset + qs->recv_window; + + if (qs->recv_max_data == recv_max_data) { + return NGX_OK; + } + + qs->recv_max_data = recv_max_data; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL flow update msd:%uL", + qs->id, qs->recv_max_data); + + frame = ngx_quic_alloc_frame(pc); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_STREAM_DATA; + frame->u.max_stream_data.id = qs->id; + frame->u.max_stream_data.limit = qs->recv_max_data; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_update_max_data(ngx_connection_t *c) +{ + uint64_t recv_max_data; + ngx_quic_frame_t *frame; + ngx_quic_connection_t *qc; + + qc = ngx_quic_get_connection(c); + + recv_max_data = qc->streams.recv_offset + qc->streams.recv_window; + + if (qc->streams.recv_max_data == recv_max_data) { + return NGX_OK; + } + + qc->streams.recv_max_data = recv_max_data; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic flow update md:%uL", qc->streams.recv_max_data); + + frame = ngx_quic_alloc_frame(c); + if (frame == NULL) { + return NGX_ERROR; + } + + frame->level = ssl_encryption_application; + frame->type = NGX_QUIC_FT_MAX_DATA; + frame->u.max_data.max_data = qc->streams.recv_max_data; + + ngx_quic_queue_frame(qc, frame); + + return NGX_OK; +} + + +static void +ngx_quic_set_event(ngx_event_t *ev) +{ + ev->ready = 1; + + if (ev->active) { + ngx_post_event(ev, &ngx_posted_events); + } +} diff --git a/src/event/quic/ngx_event_quic_streams.h b/src/event/quic/ngx_event_quic_streams.h new file mode 100644 index 0000000..fb6dbbd --- /dev/null +++ b/src/event/quic/ngx_event_quic_streams.h @@ -0,0 +1,44 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ +#define _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +ngx_int_t ngx_quic_handle_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_frame_t *frame); +void ngx_quic_handle_stream_ack(ngx_connection_t *c, + ngx_quic_frame_t *f); +ngx_int_t ngx_quic_handle_max_data_frame(ngx_connection_t *c, + ngx_quic_max_data_frame_t *f); +ngx_int_t ngx_quic_handle_streams_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_streams_blocked_frame_t *f); +ngx_int_t ngx_quic_handle_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_data_blocked_frame_t *f); +ngx_int_t ngx_quic_handle_stream_data_blocked_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stream_data_blocked_frame_t *f); +ngx_int_t ngx_quic_handle_max_stream_data_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_stream_data_frame_t *f); +ngx_int_t ngx_quic_handle_reset_stream_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_reset_stream_frame_t *f); +ngx_int_t ngx_quic_handle_stop_sending_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_stop_sending_frame_t *f); +ngx_int_t ngx_quic_handle_max_streams_frame(ngx_connection_t *c, + ngx_quic_header_t *pkt, ngx_quic_max_streams_frame_t *f); + +ngx_int_t ngx_quic_init_streams(ngx_connection_t *c); +void ngx_quic_rbtree_insert_stream(ngx_rbtree_node_t *temp, + ngx_rbtree_node_t *node, ngx_rbtree_node_t *sentinel); +ngx_quic_stream_t *ngx_quic_find_stream(ngx_rbtree_t *rbtree, + uint64_t id); +ngx_int_t ngx_quic_close_streams(ngx_connection_t *c, + ngx_quic_connection_t *qc); + +#endif /* _NGX_EVENT_QUIC_STREAMS_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_tokens.c b/src/event/quic/ngx_event_quic_tokens.c new file mode 100644 index 0000000..c1da0d4 --- /dev/null +++ b/src/event/quic/ngx_event_quic_tokens.c @@ -0,0 +1,309 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_sha1.h> +#include <ngx_event_quic_connection.h> + + +static void ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, + ngx_uint_t no_port, u_char buf[20]); + + +ngx_int_t +ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, u_char *secret, + u_char *token) +{ + ngx_str_t tmp; + + tmp.data = secret; + tmp.len = NGX_QUIC_SR_KEY_LEN; + + if (ngx_quic_derive_key(c->log, "sr_token_key", &tmp, cid, token, + NGX_QUIC_SR_TOKEN_LEN) + != NGX_OK) + { + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic stateless reset token %*xs", + (size_t) NGX_QUIC_SR_TOKEN_LEN, token); + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr, + socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid, + time_t exp, ngx_uint_t is_retry) +{ + int len, iv_len; + u_char *p, *iv; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + + u_char in[NGX_QUIC_MAX_TOKEN_SIZE]; + + ngx_quic_address_hash(sockaddr, socklen, !is_retry, in); + + p = in + 20; + + p = ngx_cpymem(p, &exp, sizeof(time_t)); + + *p++ = is_retry ? 1 : 0; + + if (odcid) { + *p++ = odcid->len; + p = ngx_cpymem(p, odcid->data, odcid->len); + + } else { + *p++ = 0; + } + + len = p - in; + + cipher = EVP_aes_256_gcm(); + iv_len = NGX_QUIC_AES_256_GCM_IV_LEN; + + if ((size_t) (iv_len + len + NGX_QUIC_AES_256_GCM_TAG_LEN) > token->len) { + ngx_log_error(NGX_LOG_ALERT, log, 0, "quic token buffer is too small"); + return NGX_ERROR; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + iv = token->data; + + if (RAND_bytes(iv, iv_len) <= 0 + || !EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv)) + { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len = iv_len; + + if (EVP_EncryptUpdate(ctx, token->data + token->len, &len, in, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + if (EVP_EncryptFinal_ex(ctx, token->data + token->len, &len) <= 0) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += len; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, + NGX_QUIC_AES_256_GCM_TAG_LEN, + token->data + token->len) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + token->len += NGX_QUIC_AES_256_GCM_TAG_LEN; + + EVP_CIPHER_CTX_free(ctx); + +#ifdef NGX_QUIC_DEBUG_PACKETS + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "quic new token len:%uz %xV", token->len, token); +#endif + + return NGX_OK; +} + + +static void +ngx_quic_address_hash(struct sockaddr *sockaddr, socklen_t socklen, + ngx_uint_t no_port, u_char buf[20]) +{ + size_t len; + u_char *data; + ngx_sha1_t sha1; + struct sockaddr_in *sin; +#if (NGX_HAVE_INET6) + struct sockaddr_in6 *sin6; +#endif + + len = (size_t) socklen; + data = (u_char *) sockaddr; + + if (no_port) { + switch (sockaddr->sa_family) { + +#if (NGX_HAVE_INET6) + case AF_INET6: + sin6 = (struct sockaddr_in6 *) sockaddr; + + len = sizeof(struct in6_addr); + data = sin6->sin6_addr.s6_addr; + + break; +#endif + + case AF_INET: + sin = (struct sockaddr_in *) sockaddr; + + len = sizeof(in_addr_t); + data = (u_char *) &sin->sin_addr; + + break; + } + } + + ngx_sha1_init(&sha1); + ngx_sha1_update(&sha1, data, len); + ngx_sha1_final(buf, &sha1); +} + + +ngx_int_t +ngx_quic_validate_token(ngx_connection_t *c, u_char *key, + ngx_quic_header_t *pkt) +{ + int len, tlen, iv_len; + u_char *iv, *p; + time_t now, exp; + size_t total; + ngx_str_t odcid; + EVP_CIPHER_CTX *ctx; + const EVP_CIPHER *cipher; + + u_char addr_hash[20]; + u_char tdec[NGX_QUIC_MAX_TOKEN_SIZE]; + +#if NGX_SUPPRESS_WARN + ngx_str_null(&odcid); +#endif + + /* Retry token or NEW_TOKEN in a previous connection */ + + cipher = EVP_aes_256_gcm(); + iv = pkt->token.data; + iv_len = NGX_QUIC_AES_256_GCM_IV_LEN; + + /* sanity checks */ + + if (pkt->token.len < (size_t) iv_len + NGX_QUIC_AES_256_GCM_TAG_LEN) { + goto garbage; + } + + if (pkt->token.len > (size_t) iv_len + NGX_QUIC_MAX_TOKEN_SIZE + + NGX_QUIC_AES_256_GCM_TAG_LEN) + { + goto garbage; + } + + ctx = EVP_CIPHER_CTX_new(); + if (ctx == NULL) { + return NGX_ERROR; + } + + if (!EVP_DecryptInit_ex(ctx, cipher, NULL, key, iv)) { + EVP_CIPHER_CTX_free(ctx); + return NGX_ERROR; + } + + p = pkt->token.data + iv_len; + len = pkt->token.len - iv_len - NGX_QUIC_AES_256_GCM_TAG_LEN; + + if (EVP_DecryptUpdate(ctx, tdec, &tlen, p, len) != 1) { + EVP_CIPHER_CTX_free(ctx); + goto garbage; + } + total = tlen; + + if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, + NGX_QUIC_AES_256_GCM_TAG_LEN, p + len) + == 0) + { + EVP_CIPHER_CTX_free(ctx); + goto garbage; + } + + if (EVP_DecryptFinal_ex(ctx, tdec + tlen, &tlen) <= 0) { + EVP_CIPHER_CTX_free(ctx); + goto garbage; + } + total += tlen; + + EVP_CIPHER_CTX_free(ctx); + + if (total < (20 + sizeof(time_t) + 2)) { + goto garbage; + } + + p = tdec + 20; + + ngx_memcpy(&exp, p, sizeof(time_t)); + p += sizeof(time_t); + + pkt->retried = (*p++ == 1); + + ngx_quic_address_hash(c->sockaddr, c->socklen, !pkt->retried, addr_hash); + + if (ngx_memcmp(tdec, addr_hash, 20) != 0) { + goto bad_token; + } + + odcid.len = *p++; + if (odcid.len) { + if (odcid.len > NGX_QUIC_MAX_CID_LEN) { + goto bad_token; + } + + if ((size_t)(tdec + total - p) < odcid.len) { + goto bad_token; + } + + odcid.data = p; + } + + now = ngx_time(); + + if (now > exp) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic expired token"); + return NGX_DECLINED; + } + + if (odcid.len) { + pkt->odcid.len = odcid.len; + pkt->odcid.data = pkt->odcid_buf; + ngx_memcpy(pkt->odcid.data, odcid.data, odcid.len); + + } else { + pkt->odcid = pkt->dcid; + } + + pkt->validated = 1; + + return NGX_OK; + +garbage: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic garbage token"); + + return NGX_ABORT; + +bad_token: + + ngx_log_error(NGX_LOG_INFO, c->log, 0, "quic invalid token"); + + return NGX_DECLINED; +} diff --git a/src/event/quic/ngx_event_quic_tokens.h b/src/event/quic/ngx_event_quic_tokens.h new file mode 100644 index 0000000..ee3fe5b --- /dev/null +++ b/src/event/quic/ngx_event_quic_tokens.h @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ +#define _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +#define NGX_QUIC_MAX_TOKEN_SIZE 64 + /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ + +#define NGX_QUIC_AES_256_GCM_IV_LEN 12 +#define NGX_QUIC_AES_256_GCM_TAG_LEN 16 + +#define NGX_QUIC_TOKEN_BUF_SIZE (NGX_QUIC_AES_256_GCM_IV_LEN \ + + NGX_QUIC_MAX_TOKEN_SIZE \ + + NGX_QUIC_AES_256_GCM_TAG_LEN) + + +ngx_int_t ngx_quic_new_sr_token(ngx_connection_t *c, ngx_str_t *cid, + u_char *secret, u_char *token); +ngx_int_t ngx_quic_new_token(ngx_log_t *log, struct sockaddr *sockaddr, + socklen_t socklen, u_char *key, ngx_str_t *token, ngx_str_t *odcid, + time_t expires, ngx_uint_t is_retry); +ngx_int_t ngx_quic_validate_token(ngx_connection_t *c, + u_char *key, ngx_quic_header_t *pkt); + +#endif /* _NGX_EVENT_QUIC_TOKENS_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_transport.c b/src/event/quic/ngx_event_quic_transport.c new file mode 100644 index 0000000..bb13447 --- /dev/null +++ b/src/event/quic/ngx_event_quic_transport.c @@ -0,0 +1,2215 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + + +#define NGX_QUIC_LONG_DCID_LEN_OFFSET 5 +#define NGX_QUIC_LONG_DCID_OFFSET 6 +#define NGX_QUIC_SHORT_DCID_OFFSET 1 + +#define NGX_QUIC_STREAM_FRAME_FIN 0x01 +#define NGX_QUIC_STREAM_FRAME_LEN 0x02 +#define NGX_QUIC_STREAM_FRAME_OFF 0x04 + + +#if (NGX_HAVE_NONALIGNED) + +#define ngx_quic_parse_uint16(p) ntohs(*(uint16_t *) (p)) +#define ngx_quic_parse_uint32(p) ntohl(*(uint32_t *) (p)) + +#define ngx_quic_write_uint16 ngx_quic_write_uint16_aligned +#define ngx_quic_write_uint32 ngx_quic_write_uint32_aligned + +#else + +#define ngx_quic_parse_uint16(p) ((p)[0] << 8 | (p)[1]) +#define ngx_quic_parse_uint32(p) \ + ((uint32_t) (p)[0] << 24 | (p)[1] << 16 | (p)[2] << 8 | (p)[3]) + +#define ngx_quic_write_uint16(p, s) \ + ((p)[0] = (u_char) ((s) >> 8), \ + (p)[1] = (u_char) (s), \ + (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32(p, s) \ + ((p)[0] = (u_char) ((s) >> 24), \ + (p)[1] = (u_char) ((s) >> 16), \ + (p)[2] = (u_char) ((s) >> 8), \ + (p)[3] = (u_char) (s), \ + (p) + sizeof(uint32_t)) + +#endif + +#define ngx_quic_write_uint64(p, s) \ + ((p)[0] = (u_char) ((s) >> 56), \ + (p)[1] = (u_char) ((s) >> 48), \ + (p)[2] = (u_char) ((s) >> 40), \ + (p)[3] = (u_char) ((s) >> 32), \ + (p)[4] = (u_char) ((s) >> 24), \ + (p)[5] = (u_char) ((s) >> 16), \ + (p)[6] = (u_char) ((s) >> 8), \ + (p)[7] = (u_char) (s), \ + (p) + sizeof(uint64_t)) + +#define ngx_quic_write_uint24(p, s) \ + ((p)[0] = (u_char) ((s) >> 16), \ + (p)[1] = (u_char) ((s) >> 8), \ + (p)[2] = (u_char) (s), \ + (p) + 3) + +#define ngx_quic_write_uint16_aligned(p, s) \ + (*(uint16_t *) (p) = htons((uint16_t) (s)), (p) + sizeof(uint16_t)) + +#define ngx_quic_write_uint32_aligned(p, s) \ + (*(uint32_t *) (p) = htonl((uint32_t) (s)), (p) + sizeof(uint32_t)) + +#define ngx_quic_build_int_set(p, value, len, bits) \ + (*(p)++ = ((value >> ((len) * 8)) & 0xff) | ((bits) << 6)) + + +static u_char *ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out); +static ngx_uint_t ngx_quic_varint_len(uint64_t value); +static void ngx_quic_build_int(u_char **pos, uint64_t value); + +static u_char *ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value); +static u_char *ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value); +static u_char *ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, + u_char **out); +static u_char *ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, + u_char *dst); + +static ngx_int_t ngx_quic_parse_short_header(ngx_quic_header_t *pkt, + size_t dcid_len); +static ngx_int_t ngx_quic_parse_long_header(ngx_quic_header_t *pkt); +static ngx_int_t ngx_quic_supported_version(uint32_t version); +static ngx_int_t ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt); + +static size_t ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp); +static size_t ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp); + +static ngx_int_t ngx_quic_frame_allowed(ngx_quic_header_t *pkt, + ngx_uint_t frame_type); +static size_t ngx_quic_create_ping(u_char *p); +static size_t ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, + ngx_chain_t *ranges); +static size_t ngx_quic_create_reset_stream(u_char *p, + ngx_quic_reset_stream_frame_t *rs); +static size_t ngx_quic_create_stop_sending(u_char *p, + ngx_quic_stop_sending_frame_t *ss); +static size_t ngx_quic_create_crypto(u_char *p, + ngx_quic_crypto_frame_t *crypto, ngx_chain_t *data); +static size_t ngx_quic_create_hs_done(u_char *p); +static size_t ngx_quic_create_new_token(u_char *p, + ngx_quic_new_token_frame_t *token, ngx_chain_t *data); +static size_t ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, + ngx_chain_t *data); +static size_t ngx_quic_create_max_streams(u_char *p, + ngx_quic_max_streams_frame_t *ms); +static size_t ngx_quic_create_max_stream_data(u_char *p, + ngx_quic_max_stream_data_frame_t *ms); +static size_t ngx_quic_create_max_data(u_char *p, + ngx_quic_max_data_frame_t *md); +static size_t ngx_quic_create_path_challenge(u_char *p, + ngx_quic_path_challenge_frame_t *pc); +static size_t ngx_quic_create_path_response(u_char *p, + ngx_quic_path_challenge_frame_t *pc); +static size_t ngx_quic_create_new_connection_id(u_char *p, + ngx_quic_new_conn_id_frame_t *rcid); +static size_t ngx_quic_create_retire_connection_id(u_char *p, + ngx_quic_retire_cid_frame_t *rcid); +static size_t ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f); + +static ngx_int_t ngx_quic_parse_transport_param(u_char *p, u_char *end, + uint16_t id, ngx_quic_tp_t *dst); + + +uint32_t ngx_quic_versions[] = { + /* QUICv1 */ + 0x00000001, +}; + +#define NGX_QUIC_NVERSIONS \ + (sizeof(ngx_quic_versions) / sizeof(ngx_quic_versions[0])) + + +static ngx_inline u_char * +ngx_quic_parse_int(u_char *pos, u_char *end, uint64_t *out) +{ + u_char *p; + uint64_t value; + ngx_uint_t len; + + if (pos >= end) { + return NULL; + } + + p = pos; + len = 1 << (*p >> 6); + + value = *p++ & 0x3f; + + if ((size_t)(end - p) < (len - 1)) { + return NULL; + } + + while (--len) { + value = (value << 8) + *p++; + } + + *out = value; + + return p; +} + + +static ngx_inline u_char * +ngx_quic_read_uint8(u_char *pos, u_char *end, uint8_t *value) +{ + if ((size_t)(end - pos) < 1) { + return NULL; + } + + *value = *pos; + + return pos + 1; +} + + +static ngx_inline u_char * +ngx_quic_read_uint32(u_char *pos, u_char *end, uint32_t *value) +{ + if ((size_t)(end - pos) < sizeof(uint32_t)) { + return NULL; + } + + *value = ngx_quic_parse_uint32(pos); + + return pos + sizeof(uint32_t); +} + + +static ngx_inline u_char * +ngx_quic_read_bytes(u_char *pos, u_char *end, size_t len, u_char **out) +{ + if ((size_t)(end - pos) < len) { + return NULL; + } + + *out = pos; + + return pos + len; +} + + +static u_char * +ngx_quic_copy_bytes(u_char *pos, u_char *end, size_t len, u_char *dst) +{ + if ((size_t)(end - pos) < len) { + return NULL; + } + + ngx_memcpy(dst, pos, len); + + return pos + len; +} + + +static ngx_inline ngx_uint_t +ngx_quic_varint_len(uint64_t value) +{ + if (value < (1 << 6)) { + return 1; + } + + if (value < (1 << 14)) { + return 2; + } + + if (value < (1 << 30)) { + return 4; + } + + return 8; +} + + +static ngx_inline void +ngx_quic_build_int(u_char **pos, uint64_t value) +{ + u_char *p; + + p = *pos; + + if (value < (1 << 6)) { + ngx_quic_build_int_set(p, value, 0, 0); + + } else if (value < (1 << 14)) { + ngx_quic_build_int_set(p, value, 1, 1); + ngx_quic_build_int_set(p, value, 0, 0); + + } else if (value < (1 << 30)) { + ngx_quic_build_int_set(p, value, 3, 2); + ngx_quic_build_int_set(p, value, 2, 0); + ngx_quic_build_int_set(p, value, 1, 0); + ngx_quic_build_int_set(p, value, 0, 0); + + } else { + ngx_quic_build_int_set(p, value, 7, 3); + ngx_quic_build_int_set(p, value, 6, 0); + ngx_quic_build_int_set(p, value, 5, 0); + ngx_quic_build_int_set(p, value, 4, 0); + ngx_quic_build_int_set(p, value, 3, 0); + ngx_quic_build_int_set(p, value, 2, 0); + ngx_quic_build_int_set(p, value, 1, 0); + ngx_quic_build_int_set(p, value, 0, 0); + } + + *pos = p; +} + + +ngx_int_t +ngx_quic_parse_packet(ngx_quic_header_t *pkt) +{ + if (!ngx_quic_long_pkt(pkt->flags)) { + pkt->level = ssl_encryption_application; + + if (ngx_quic_parse_short_header(pkt, NGX_QUIC_SERVER_CID_LEN) != NGX_OK) + { + return NGX_ERROR; + } + + return NGX_OK; + } + + if (ngx_quic_parse_long_header(pkt) != NGX_OK) { + return NGX_ERROR; + } + + if (pkt->version == 0) { + /* version negotiation */ + return NGX_ERROR; + } + + if (!ngx_quic_supported_version(pkt->version)) { + return NGX_ABORT; + } + + if (ngx_quic_parse_long_header_v1(pkt) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_parse_short_header(ngx_quic_header_t *pkt, size_t dcid_len) +{ + u_char *p, *end; + + p = pkt->raw->pos; + end = pkt->data + pkt->len; + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx short flags:%xd", pkt->flags); + + if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); + return NGX_ERROR; + } + + pkt->dcid.len = dcid_len; + + p = ngx_quic_read_bytes(p, end, dcid_len, &pkt->dcid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_parse_long_header(ngx_quic_header_t *pkt) +{ + u_char *p, *end; + uint8_t idlen; + + p = pkt->raw->pos; + end = pkt->data + pkt->len; + + p = ngx_quic_read_uint32(p, end, &pkt->version); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read version"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx long flags:%xd version:%xD", + pkt->flags, pkt->version); + + if (!(pkt->flags & NGX_QUIC_PKT_FIXED_BIT)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic fixed bit is not set"); + return NGX_ERROR; + } + + p = ngx_quic_read_uint8(p, end, &idlen); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid len"); + return NGX_ERROR; + } + + if (idlen > NGX_QUIC_CID_LEN_MAX) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet dcid is too long"); + return NGX_ERROR; + } + + pkt->dcid.len = idlen; + + p = ngx_quic_read_bytes(p, end, idlen, &pkt->dcid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read dcid"); + return NGX_ERROR; + } + + p = ngx_quic_read_uint8(p, end, &idlen); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read scid len"); + return NGX_ERROR; + } + + if (idlen > NGX_QUIC_CID_LEN_MAX) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet scid is too long"); + return NGX_ERROR; + } + + pkt->scid.len = idlen; + + p = ngx_quic_read_bytes(p, end, idlen, &pkt->scid.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet is too small to read scid"); + return NGX_ERROR; + } + + pkt->raw->pos = p; + + return NGX_OK; +} + + +static ngx_int_t +ngx_quic_supported_version(uint32_t version) +{ + ngx_uint_t i; + + for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { + if (ngx_quic_versions[i] == version) { + return 1; + } + } + + return 0; +} + + +static ngx_int_t +ngx_quic_parse_long_header_v1(ngx_quic_header_t *pkt) +{ + u_char *p, *end; + uint64_t varint; + + p = pkt->raw->pos; + end = pkt->raw->last; + + pkt->log->action = "parsing quic long header"; + + if (ngx_quic_pkt_in(pkt->flags)) { + + if (pkt->len < NGX_QUIC_MIN_INITIAL_SIZE) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic UDP datagram is too small for initial packet"); + return NGX_DECLINED; + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to parse token length"); + return NGX_ERROR; + } + + pkt->token.len = varint; + + p = ngx_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic packet too small to read token data"); + return NGX_ERROR; + } + + pkt->level = ssl_encryption_initial; + + } else if (ngx_quic_pkt_zrtt(pkt->flags)) { + pkt->level = ssl_encryption_early_data; + + } else if (ngx_quic_pkt_hs(pkt->flags)) { + pkt->level = ssl_encryption_handshake; + + } else { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic bad packet type"); + return NGX_DECLINED; + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic bad packet length"); + return NGX_ERROR; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic packet rx %s len:%uL", + ngx_quic_level_name(pkt->level), varint); + + if (varint > (uint64_t) ((pkt->data + pkt->len) - p)) { + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, "quic truncated %s packet", + ngx_quic_level_name(pkt->level)); + return NGX_ERROR; + } + + pkt->raw->pos = p; + pkt->len = p + varint - pkt->data; + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_get_packet_dcid(ngx_log_t *log, u_char *data, size_t n, + ngx_str_t *dcid) +{ + size_t len, offset; + + if (n == 0) { + goto failed; + } + + if (ngx_quic_long_pkt(*data)) { + if (n < NGX_QUIC_LONG_DCID_LEN_OFFSET + 1) { + goto failed; + } + + len = data[NGX_QUIC_LONG_DCID_LEN_OFFSET]; + offset = NGX_QUIC_LONG_DCID_OFFSET; + + } else { + len = NGX_QUIC_SERVER_CID_LEN; + offset = NGX_QUIC_SHORT_DCID_OFFSET; + } + + if (n < len + offset) { + goto failed; + } + + dcid->len = len; + dcid->data = &data[offset]; + + return NGX_OK; + +failed: + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, "quic malformed packet"); + + return NGX_ERROR; +} + + +size_t +ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out) +{ + u_char *p, *start; + ngx_uint_t i; + + p = start = out; + + *p++ = pkt->flags; + + /* + * The Version field of a Version Negotiation packet + * MUST be set to 0x00000000 + */ + p = ngx_quic_write_uint32(p, 0); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + for (i = 0; i < NGX_QUIC_NVERSIONS; i++) { + p = ngx_quic_write_uint32(p, ngx_quic_versions[i]); + } + + return p - start; +} + + +/* returns the amount of payload quic packet of "pkt_len" size may fit or 0 */ +size_t +ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len) +{ + size_t len; + + if (ngx_quic_short_pkt(pkt->flags)) { + + len = 1 + pkt->dcid.len + pkt->num_len + NGX_QUIC_TAG_LEN; + if (len > pkt_len) { + return 0; + } + + return pkt_len - len; + } + + /* flags, version, dcid and scid with lengths and zero-length token */ + len = 5 + 2 + pkt->dcid.len + pkt->scid.len + + (pkt->level == ssl_encryption_initial ? 1 : 0); + + if (len > pkt_len) { + return 0; + } + + /* (pkt_len - len) is 'remainder' packet length (see RFC 9000, 17.2) */ + len += ngx_quic_varint_len(pkt_len - len) + + pkt->num_len + NGX_QUIC_TAG_LEN; + + if (len > pkt_len) { + return 0; + } + + return pkt_len - len; +} + + +size_t +ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, u_char **pnp) +{ + return ngx_quic_short_pkt(pkt->flags) + ? ngx_quic_create_short_header(pkt, out, pnp) + : ngx_quic_create_long_header(pkt, out, pnp); +} + + +static size_t +ngx_quic_create_long_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp) +{ + size_t rem_len; + u_char *p, *start; + + rem_len = pkt->num_len + pkt->payload.len + NGX_QUIC_TAG_LEN; + + if (out == NULL) { + return 5 + 2 + pkt->dcid.len + pkt->scid.len + + ngx_quic_varint_len(rem_len) + pkt->num_len + + (pkt->level == ssl_encryption_initial ? 1 : 0); + } + + p = start = out; + + *p++ = pkt->flags; + + p = ngx_quic_write_uint32(p, pkt->version); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + if (pkt->level == ssl_encryption_initial) { + ngx_quic_build_int(&p, 0); + } + + ngx_quic_build_int(&p, rem_len); + + *pnp = p; + + switch (pkt->num_len) { + case 1: + *p++ = pkt->trunc; + break; + case 2: + p = ngx_quic_write_uint16(p, pkt->trunc); + break; + case 3: + p = ngx_quic_write_uint24(p, pkt->trunc); + break; + case 4: + p = ngx_quic_write_uint32(p, pkt->trunc); + break; + } + + return p - start; +} + + +static size_t +ngx_quic_create_short_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp) +{ + u_char *p, *start; + + if (out == NULL) { + return 1 + pkt->dcid.len + pkt->num_len; + } + + p = start = out; + + *p++ = pkt->flags; + + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *pnp = p; + + switch (pkt->num_len) { + case 1: + *p++ = pkt->trunc; + break; + case 2: + p = ngx_quic_write_uint16(p, pkt->trunc); + break; + case 3: + p = ngx_quic_write_uint24(p, pkt->trunc); + break; + case 4: + p = ngx_quic_write_uint32(p, pkt->trunc); + break; + } + + return p - start; +} + + +size_t +ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, + u_char **start) +{ + u_char *p; + + p = out; + + *p++ = pkt->odcid.len; + p = ngx_cpymem(p, pkt->odcid.data, pkt->odcid.len); + + *start = p; + + *p++ = 0xff; + + p = ngx_quic_write_uint32(p, pkt->version); + + *p++ = pkt->dcid.len; + p = ngx_cpymem(p, pkt->dcid.data, pkt->dcid.len); + + *p++ = pkt->scid.len; + p = ngx_cpymem(p, pkt->scid.data, pkt->scid.len); + + p = ngx_cpymem(p, pkt->token.data, pkt->token.len); + + return p - out; +} + + +ssize_t +ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, + ngx_quic_frame_t *f) +{ + u_char *p; + uint64_t varint; + ngx_buf_t *b; + ngx_uint_t i; + + b = f->data->buf; + + p = start; + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to obtain quic frame type"); + return NGX_ERROR; + } + + if (varint > NGX_QUIC_FT_LAST) { + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic unknown frame type 0x%xL", varint); + return NGX_ERROR; + } + + f->type = varint; + + if (ngx_quic_frame_allowed(pkt, f->type) != NGX_OK) { + pkt->error = NGX_QUIC_ERR_PROTOCOL_VIOLATION; + return NGX_ERROR; + } + + switch (f->type) { + + case NGX_QUIC_FT_CRYPTO: + + p = ngx_quic_parse_int(p, end, &f->u.crypto.offset); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.crypto.length); + if (p == NULL) { + goto error; + } + + p = ngx_quic_read_bytes(p, end, f->u.crypto.length, &b->pos); + if (p == NULL) { + goto error; + } + + b->last = p; + + break; + + case NGX_QUIC_FT_PADDING: + + while (p < end && *p == NGX_QUIC_FT_PADDING) { + p++; + } + + break; + + case NGX_QUIC_FT_ACK: + case NGX_QUIC_FT_ACK_ECN: + + p = ngx_quic_parse_int(p, end, &f->u.ack.largest); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.delay); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.range_count); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.first_range); + if (p == NULL) { + goto error; + } + + b->pos = p; + + /* process all ranges to get bounds, values are ignored */ + for (i = 0; i < f->u.ack.range_count; i++) { + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; + } + } + + b->last = p; + + f->u.ack.ranges_length = b->last - b->pos; + + if (f->type == NGX_QUIC_FT_ACK_ECN) { + + p = ngx_quic_parse_int(p, end, &f->u.ack.ect0); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.ect1); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ack.ce); + if (p == NULL) { + goto error; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, pkt->log, 0, + "quic ACK ECN counters ect0:%uL ect1:%uL ce:%uL", + f->u.ack.ect0, f->u.ack.ect1, f->u.ack.ce); + } + + break; + + case NGX_QUIC_FT_PING: + break; + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + + p = ngx_quic_parse_int(p, end, &f->u.ncid.seqnum); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.ncid.retire); + if (p == NULL) { + goto error; + } + + if (f->u.ncid.retire > f->u.ncid.seqnum) { + goto error; + } + + p = ngx_quic_read_uint8(p, end, &f->u.ncid.len); + if (p == NULL) { + goto error; + } + + if (f->u.ncid.len < 1 || f->u.ncid.len > NGX_QUIC_CID_LEN_MAX) { + goto error; + } + + p = ngx_quic_copy_bytes(p, end, f->u.ncid.len, f->u.ncid.cid); + if (p == NULL) { + goto error; + } + + p = ngx_quic_copy_bytes(p, end, NGX_QUIC_SR_TOKEN_LEN, f->u.ncid.srt); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + + p = ngx_quic_parse_int(p, end, &f->u.retire_cid.sequence_number); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + + p = ngx_quic_parse_int(p, end, &f->u.close.error_code); + if (p == NULL) { + goto error; + } + + if (f->type == NGX_QUIC_FT_CONNECTION_CLOSE) { + p = ngx_quic_parse_int(p, end, &f->u.close.frame_type); + if (p == NULL) { + goto error; + } + } + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; + } + + f->u.close.reason.len = varint; + + p = ngx_quic_read_bytes(p, end, f->u.close.reason.len, + &f->u.close.reason.data); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STREAM: + case NGX_QUIC_FT_STREAM1: + case NGX_QUIC_FT_STREAM2: + case NGX_QUIC_FT_STREAM3: + case NGX_QUIC_FT_STREAM4: + case NGX_QUIC_FT_STREAM5: + case NGX_QUIC_FT_STREAM6: + case NGX_QUIC_FT_STREAM7: + + f->u.stream.fin = (f->type & NGX_QUIC_STREAM_FRAME_FIN) ? 1 : 0; + + p = ngx_quic_parse_int(p, end, &f->u.stream.stream_id); + if (p == NULL) { + goto error; + } + + if (f->type & NGX_QUIC_STREAM_FRAME_OFF) { + f->u.stream.off = 1; + + p = ngx_quic_parse_int(p, end, &f->u.stream.offset); + if (p == NULL) { + goto error; + } + + } else { + f->u.stream.off = 0; + f->u.stream.offset = 0; + } + + if (f->type & NGX_QUIC_STREAM_FRAME_LEN) { + f->u.stream.len = 1; + + p = ngx_quic_parse_int(p, end, &f->u.stream.length); + if (p == NULL) { + goto error; + } + + } else { + f->u.stream.len = 0; + f->u.stream.length = end - p; /* up to packet end */ + } + + p = ngx_quic_read_bytes(p, end, f->u.stream.length, &b->pos); + if (p == NULL) { + goto error; + } + + b->last = p; + + f->type = NGX_QUIC_FT_STREAM; + break; + + case NGX_QUIC_FT_MAX_DATA: + + p = ngx_quic_parse_int(p, end, &f->u.max_data.max_data); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_RESET_STREAM: + + p = ngx_quic_parse_int(p, end, &f->u.reset_stream.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.reset_stream.error_code); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.reset_stream.final_size); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STOP_SENDING: + + p = ngx_quic_parse_int(p, end, &f->u.stop_sending.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.stop_sending.error_code); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STREAMS_BLOCKED: + case NGX_QUIC_FT_STREAMS_BLOCKED2: + + p = ngx_quic_parse_int(p, end, &f->u.streams_blocked.limit); + if (p == NULL) { + goto error; + } + + if (f->u.streams_blocked.limit > 0x1000000000000000) { + goto error; + } + + f->u.streams_blocked.bidi = + (f->type == NGX_QUIC_FT_STREAMS_BLOCKED) ? 1 : 0; + break; + + case NGX_QUIC_FT_MAX_STREAMS: + case NGX_QUIC_FT_MAX_STREAMS2: + + p = ngx_quic_parse_int(p, end, &f->u.max_streams.limit); + if (p == NULL) { + goto error; + } + + if (f->u.max_streams.limit > 0x1000000000000000) { + goto error; + } + + f->u.max_streams.bidi = (f->type == NGX_QUIC_FT_MAX_STREAMS) ? 1 : 0; + + break; + + case NGX_QUIC_FT_MAX_STREAM_DATA: + + p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.max_stream_data.limit); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_DATA_BLOCKED: + + p = ngx_quic_parse_int(p, end, &f->u.data_blocked.limit); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_STREAM_DATA_BLOCKED: + + p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.id); + if (p == NULL) { + goto error; + } + + p = ngx_quic_parse_int(p, end, &f->u.stream_data_blocked.limit); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_PATH_CHALLENGE: + + p = ngx_quic_copy_bytes(p, end, 8, f->u.path_challenge.data); + if (p == NULL) { + goto error; + } + + break; + + case NGX_QUIC_FT_PATH_RESPONSE: + + p = ngx_quic_copy_bytes(p, end, 8, f->u.path_response.data); + if (p == NULL) { + goto error; + } + + break; + + default: + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic unknown frame type 0x%xi", f->type); + return NGX_ERROR; + } + + f->level = pkt->level; +#if (NGX_DEBUG) + f->pnum = pkt->pn; +#endif + + return p - start; + +error: + + pkt->error = NGX_QUIC_ERR_FRAME_ENCODING_ERROR; + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic failed to parse frame type:0x%xi", f->type); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_quic_frame_allowed(ngx_quic_header_t *pkt, ngx_uint_t frame_type) +{ + uint8_t ptype; + + /* + * RFC 9000, 12.4. Frames and Frame Types: Table 3 + * + * Frame permissions per packet: 4 bits: IH01 + */ + static uint8_t ngx_quic_frame_masks[] = { + /* PADDING */ 0xF, + /* PING */ 0xF, + /* ACK */ 0xD, + /* ACK_ECN */ 0xD, + /* RESET_STREAM */ 0x3, + /* STOP_SENDING */ 0x3, + /* CRYPTO */ 0xD, + /* NEW_TOKEN */ 0x0, /* only sent by server */ + /* STREAM */ 0x3, + /* STREAM1 */ 0x3, + /* STREAM2 */ 0x3, + /* STREAM3 */ 0x3, + /* STREAM4 */ 0x3, + /* STREAM5 */ 0x3, + /* STREAM6 */ 0x3, + /* STREAM7 */ 0x3, + /* MAX_DATA */ 0x3, + /* MAX_STREAM_DATA */ 0x3, + /* MAX_STREAMS */ 0x3, + /* MAX_STREAMS2 */ 0x3, + /* DATA_BLOCKED */ 0x3, + /* STREAM_DATA_BLOCKED */ 0x3, + /* STREAMS_BLOCKED */ 0x3, + /* STREAMS_BLOCKED2 */ 0x3, + /* NEW_CONNECTION_ID */ 0x3, + /* RETIRE_CONNECTION_ID */ 0x3, + /* PATH_CHALLENGE */ 0x3, + /* PATH_RESPONSE */ 0x1, + /* CONNECTION_CLOSE */ 0xF, + /* CONNECTION_CLOSE2 */ 0x3, + /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ + }; + + if (ngx_quic_long_pkt(pkt->flags)) { + + if (ngx_quic_pkt_in(pkt->flags)) { + ptype = 8; /* initial */ + + } else if (ngx_quic_pkt_hs(pkt->flags)) { + ptype = 4; /* handshake */ + + } else { + ptype = 2; /* zero-rtt */ + } + + } else { + ptype = 1; /* application data */ + } + + if (ptype & ngx_quic_frame_masks[frame_type]) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, pkt->log, 0, + "quic frame type 0x%xi is not " + "allowed in packet with flags 0x%xd", + frame_type, pkt->flags); + + return NGX_DECLINED; +} + + +ssize_t +ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, u_char *end, + uint64_t *gap, uint64_t *range) +{ + u_char *p; + + p = start; + + p = ngx_quic_parse_int(p, end, gap); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse ack frame gap"); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, range); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse ack frame range"); + return NGX_ERROR; + } + + return p - start; +} + + +size_t +ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(gap); + len += ngx_quic_varint_len(range); + return len; + } + + start = p; + + ngx_quic_build_int(&p, gap); + ngx_quic_build_int(&p, range); + + return p - start; +} + + +ssize_t +ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f) +{ + /* + * RFC 9002, 2. Conventions and Definitions + * + * Ack-eliciting frames: All frames other than ACK, PADDING, and + * CONNECTION_CLOSE are considered ack-eliciting. + */ + f->need_ack = 1; + + switch (f->type) { + case NGX_QUIC_FT_PING: + return ngx_quic_create_ping(p); + + case NGX_QUIC_FT_ACK: + f->need_ack = 0; + return ngx_quic_create_ack(p, &f->u.ack, f->data); + + case NGX_QUIC_FT_RESET_STREAM: + return ngx_quic_create_reset_stream(p, &f->u.reset_stream); + + case NGX_QUIC_FT_STOP_SENDING: + return ngx_quic_create_stop_sending(p, &f->u.stop_sending); + + case NGX_QUIC_FT_CRYPTO: + return ngx_quic_create_crypto(p, &f->u.crypto, f->data); + + case NGX_QUIC_FT_HANDSHAKE_DONE: + return ngx_quic_create_hs_done(p); + + case NGX_QUIC_FT_NEW_TOKEN: + return ngx_quic_create_new_token(p, &f->u.token, f->data); + + case NGX_QUIC_FT_STREAM: + return ngx_quic_create_stream(p, &f->u.stream, f->data); + + case NGX_QUIC_FT_CONNECTION_CLOSE: + case NGX_QUIC_FT_CONNECTION_CLOSE_APP: + f->need_ack = 0; + return ngx_quic_create_close(p, f); + + case NGX_QUIC_FT_MAX_STREAMS: + return ngx_quic_create_max_streams(p, &f->u.max_streams); + + case NGX_QUIC_FT_MAX_STREAM_DATA: + return ngx_quic_create_max_stream_data(p, &f->u.max_stream_data); + + case NGX_QUIC_FT_MAX_DATA: + return ngx_quic_create_max_data(p, &f->u.max_data); + + case NGX_QUIC_FT_PATH_CHALLENGE: + return ngx_quic_create_path_challenge(p, &f->u.path_challenge); + + case NGX_QUIC_FT_PATH_RESPONSE: + return ngx_quic_create_path_response(p, &f->u.path_response); + + case NGX_QUIC_FT_NEW_CONNECTION_ID: + return ngx_quic_create_new_connection_id(p, &f->u.ncid); + + case NGX_QUIC_FT_RETIRE_CONNECTION_ID: + return ngx_quic_create_retire_connection_id(p, &f->u.retire_cid); + + default: + /* BUG: unsupported frame type generated */ + return NGX_ERROR; + } +} + + +static size_t +ngx_quic_create_ping(u_char *p) +{ + u_char *start; + + if (p == NULL) { + return ngx_quic_varint_len(NGX_QUIC_FT_PING); + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PING); + + return p - start; +} + + +static size_t +ngx_quic_create_ack(u_char *p, ngx_quic_ack_frame_t *ack, ngx_chain_t *ranges) +{ + size_t len; + u_char *start; + ngx_buf_t *b; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_ACK); + len += ngx_quic_varint_len(ack->largest); + len += ngx_quic_varint_len(ack->delay); + len += ngx_quic_varint_len(ack->range_count); + len += ngx_quic_varint_len(ack->first_range); + len += ack->ranges_length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_ACK); + ngx_quic_build_int(&p, ack->largest); + ngx_quic_build_int(&p, ack->delay); + ngx_quic_build_int(&p, ack->range_count); + ngx_quic_build_int(&p, ack->first_range); + + while (ranges) { + b = ranges->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + ranges = ranges->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_reset_stream(u_char *p, ngx_quic_reset_stream_frame_t *rs) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_RESET_STREAM); + len += ngx_quic_varint_len(rs->id); + len += ngx_quic_varint_len(rs->error_code); + len += ngx_quic_varint_len(rs->final_size); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_RESET_STREAM); + ngx_quic_build_int(&p, rs->id); + ngx_quic_build_int(&p, rs->error_code); + ngx_quic_build_int(&p, rs->final_size); + + return p - start; +} + + +static size_t +ngx_quic_create_stop_sending(u_char *p, ngx_quic_stop_sending_frame_t *ss) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_STOP_SENDING); + len += ngx_quic_varint_len(ss->id); + len += ngx_quic_varint_len(ss->error_code); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_STOP_SENDING); + ngx_quic_build_int(&p, ss->id); + ngx_quic_build_int(&p, ss->error_code); + + return p - start; +} + + +static size_t +ngx_quic_create_crypto(u_char *p, ngx_quic_crypto_frame_t *crypto, + ngx_chain_t *data) +{ + size_t len; + u_char *start; + ngx_buf_t *b; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_CRYPTO); + len += ngx_quic_varint_len(crypto->offset); + len += ngx_quic_varint_len(crypto->length); + len += crypto->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_CRYPTO); + ngx_quic_build_int(&p, crypto->offset); + ngx_quic_build_int(&p, crypto->length); + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_hs_done(u_char *p) +{ + u_char *start; + + if (p == NULL) { + return ngx_quic_varint_len(NGX_QUIC_FT_HANDSHAKE_DONE); + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_HANDSHAKE_DONE); + + return p - start; +} + + +static size_t +ngx_quic_create_new_token(u_char *p, ngx_quic_new_token_frame_t *token, + ngx_chain_t *data) +{ + size_t len; + u_char *start; + ngx_buf_t *b; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_TOKEN); + len += ngx_quic_varint_len(token->length); + len += token->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_TOKEN); + ngx_quic_build_int(&p, token->length); + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_stream(u_char *p, ngx_quic_stream_frame_t *sf, + ngx_chain_t *data) +{ + size_t len; + u_char *start, type; + ngx_buf_t *b; + + type = NGX_QUIC_FT_STREAM; + + if (sf->off) { + type |= NGX_QUIC_STREAM_FRAME_OFF; + } + + if (sf->len) { + type |= NGX_QUIC_STREAM_FRAME_LEN; + } + + if (sf->fin) { + type |= NGX_QUIC_STREAM_FRAME_FIN; + } + + if (p == NULL) { + len = ngx_quic_varint_len(type); + len += ngx_quic_varint_len(sf->stream_id); + + if (sf->off) { + len += ngx_quic_varint_len(sf->offset); + } + + if (sf->len) { + len += ngx_quic_varint_len(sf->length); + } + + len += sf->length; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, type); + ngx_quic_build_int(&p, sf->stream_id); + + if (sf->off) { + ngx_quic_build_int(&p, sf->offset); + } + + if (sf->len) { + ngx_quic_build_int(&p, sf->length); + } + + while (data) { + b = data->buf; + p = ngx_cpymem(p, b->pos, b->last - b->pos); + data = data->next; + } + + return p - start; +} + + +static size_t +ngx_quic_create_max_streams(u_char *p, ngx_quic_max_streams_frame_t *ms) +{ + size_t len; + u_char *start; + ngx_uint_t type; + + type = ms->bidi ? NGX_QUIC_FT_MAX_STREAMS : NGX_QUIC_FT_MAX_STREAMS2; + + if (p == NULL) { + len = ngx_quic_varint_len(type); + len += ngx_quic_varint_len(ms->limit); + return len; + } + + start = p; + + ngx_quic_build_int(&p, type); + ngx_quic_build_int(&p, ms->limit); + + return p - start; +} + + +static ngx_int_t +ngx_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, + ngx_quic_tp_t *dst) +{ + uint64_t varint; + ngx_str_t str; + + varint = 0; + ngx_str_null(&str); + + switch (id) { + + case NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION: + /* zero-length option */ + if (end - p != 0) { + return NGX_ERROR; + } + dst->disable_active_migration = 1; + return NGX_OK; + + case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: + case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: + case NGX_QUIC_TP_INITIAL_MAX_DATA: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: + case NGX_QUIC_TP_ACK_DELAY_EXPONENT: + case NGX_QUIC_TP_MAX_ACK_DELAY: + case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: + + p = ngx_quic_parse_int(p, end, &varint); + if (p == NULL) { + return NGX_ERROR; + } + break; + + case NGX_QUIC_TP_INITIAL_SCID: + + str.len = end - p; + str.data = p; + break; + + default: + return NGX_DECLINED; + } + + switch (id) { + + case NGX_QUIC_TP_MAX_IDLE_TIMEOUT: + dst->max_idle_timeout = varint; + break; + + case NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE: + dst->max_udp_payload_size = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_DATA: + dst->initial_max_data = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL: + dst->initial_max_stream_data_bidi_local = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE: + dst->initial_max_stream_data_bidi_remote = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI: + dst->initial_max_stream_data_uni = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI: + dst->initial_max_streams_bidi = varint; + break; + + case NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI: + dst->initial_max_streams_uni = varint; + break; + + case NGX_QUIC_TP_ACK_DELAY_EXPONENT: + dst->ack_delay_exponent = varint; + break; + + case NGX_QUIC_TP_MAX_ACK_DELAY: + dst->max_ack_delay = varint; + break; + + case NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT: + dst->active_connection_id_limit = varint; + break; + + case NGX_QUIC_TP_INITIAL_SCID: + dst->initial_scid = str; + break; + + default: + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_quic_parse_transport_params(u_char *p, u_char *end, ngx_quic_tp_t *tp, + ngx_log_t *log) +{ + uint64_t id, len; + ngx_int_t rc; + + while (p < end) { + p = ngx_quic_parse_int(p, end, &id); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse transport param id"); + return NGX_ERROR; + } + + switch (id) { + case NGX_QUIC_TP_ORIGINAL_DCID: + case NGX_QUIC_TP_PREFERRED_ADDRESS: + case NGX_QUIC_TP_RETRY_SCID: + case NGX_QUIC_TP_SR_TOKEN: + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic client sent forbidden transport param" + " id:0x%xL", id); + return NGX_ERROR; + } + + p = ngx_quic_parse_int(p, end, &len); + if (p == NULL) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse" + " transport param id:0x%xL length", id); + return NGX_ERROR; + } + + if ((size_t) (end - p) < len) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse" + " transport param id:0x%xL, data length %uL too long", + id, len); + return NGX_ERROR; + } + + rc = ngx_quic_parse_transport_param(p, p + len, id, tp); + + if (rc == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic failed to parse" + " transport param id:0x%xL data", id); + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic %s transport param id:0x%xL, skipped", + (id % 31 == 27) ? "reserved" : "unknown", id); + } + + p += len; + } + + if (p != end) { + ngx_log_error(NGX_LOG_INFO, log, 0, + "quic trailing garbage in" + " transport parameters: bytes:%ui", + end - p); + return NGX_ERROR; + } + + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, log, 0, + "quic transport parameters parsed ok"); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp disable active migration: %ui", + tp->disable_active_migration); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp idle_timeout:%ui", + tp->max_idle_timeout); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_udp_payload_size:%ui", + tp->max_udp_payload_size); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_data:%ui", + tp->initial_max_data); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_bidi_local:%ui", + tp->initial_max_stream_data_bidi_local); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_bidi_remote:%ui", + tp->initial_max_stream_data_bidi_remote); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp max_stream_data_uni:%ui", + tp->initial_max_stream_data_uni); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial_max_streams_bidi:%ui", + tp->initial_max_streams_bidi); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial_max_streams_uni:%ui", + tp->initial_max_streams_uni); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp ack_delay_exponent:%ui", + tp->ack_delay_exponent); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, "quic tp max_ack_delay:%ui", + tp->max_ack_delay); + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp active_connection_id_limit:%ui", + tp->active_connection_id_limit); + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, log, 0, + "quic tp initial source_connection_id len:%uz %xV", + tp->initial_scid.len, &tp->initial_scid); + + return NGX_OK; +} + + +static size_t +ngx_quic_create_max_stream_data(u_char *p, ngx_quic_max_stream_data_frame_t *ms) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_STREAM_DATA); + len += ngx_quic_varint_len(ms->id); + len += ngx_quic_varint_len(ms->limit); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_STREAM_DATA); + ngx_quic_build_int(&p, ms->id); + ngx_quic_build_int(&p, ms->limit); + + return p - start; +} + + +static size_t +ngx_quic_create_max_data(u_char *p, ngx_quic_max_data_frame_t *md) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_MAX_DATA); + len += ngx_quic_varint_len(md->max_data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_MAX_DATA); + ngx_quic_build_int(&p, md->max_data); + + return p - start; +} + + +static size_t +ngx_quic_create_path_challenge(u_char *p, ngx_quic_path_challenge_frame_t *pc) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_CHALLENGE); + len += sizeof(pc->data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_CHALLENGE); + p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); + + return p - start; +} + + +static size_t +ngx_quic_create_path_response(u_char *p, ngx_quic_path_challenge_frame_t *pc) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_PATH_RESPONSE); + len += sizeof(pc->data); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_PATH_RESPONSE); + p = ngx_cpymem(p, &pc->data, sizeof(pc->data)); + + return p - start; +} + + +static size_t +ngx_quic_create_new_connection_id(u_char *p, ngx_quic_new_conn_id_frame_t *ncid) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_NEW_CONNECTION_ID); + len += ngx_quic_varint_len(ncid->seqnum); + len += ngx_quic_varint_len(ncid->retire); + len++; + len += ncid->len; + len += NGX_QUIC_SR_TOKEN_LEN; + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_NEW_CONNECTION_ID); + ngx_quic_build_int(&p, ncid->seqnum); + ngx_quic_build_int(&p, ncid->retire); + *p++ = ncid->len; + p = ngx_cpymem(p, ncid->cid, ncid->len); + p = ngx_cpymem(p, ncid->srt, NGX_QUIC_SR_TOKEN_LEN); + + return p - start; +} + + +static size_t +ngx_quic_create_retire_connection_id(u_char *p, + ngx_quic_retire_cid_frame_t *rcid) +{ + size_t len; + u_char *start; + + if (p == NULL) { + len = ngx_quic_varint_len(NGX_QUIC_FT_RETIRE_CONNECTION_ID); + len += ngx_quic_varint_len(rcid->sequence_number); + return len; + } + + start = p; + + ngx_quic_build_int(&p, NGX_QUIC_FT_RETIRE_CONNECTION_ID); + ngx_quic_build_int(&p, rcid->sequence_number); + + return p - start; +} + + +ngx_int_t +ngx_quic_init_transport_params(ngx_quic_tp_t *tp, ngx_quic_conf_t *qcf) +{ + ngx_uint_t nstreams; + + ngx_memzero(tp, sizeof(ngx_quic_tp_t)); + + /* + * set by ngx_memzero(): + * + * tp->disable_active_migration = 0; + * tp->original_dcid = { 0, NULL }; + * tp->initial_scid = { 0, NULL }; + * tp->retry_scid = { 0, NULL }; + * tp->sr_token = { 0 } + * tp->sr_enabled = 0 + * tp->preferred_address = NULL + */ + + tp->max_idle_timeout = qcf->idle_timeout; + + tp->max_udp_payload_size = NGX_QUIC_MAX_UDP_PAYLOAD_SIZE; + + nstreams = qcf->max_concurrent_streams_bidi + + qcf->max_concurrent_streams_uni; + + tp->initial_max_data = nstreams * qcf->stream_buffer_size; + tp->initial_max_stream_data_bidi_local = qcf->stream_buffer_size; + tp->initial_max_stream_data_bidi_remote = qcf->stream_buffer_size; + tp->initial_max_stream_data_uni = qcf->stream_buffer_size; + + tp->initial_max_streams_bidi = qcf->max_concurrent_streams_bidi; + tp->initial_max_streams_uni = qcf->max_concurrent_streams_uni; + + tp->max_ack_delay = NGX_QUIC_DEFAULT_MAX_ACK_DELAY; + tp->ack_delay_exponent = NGX_QUIC_DEFAULT_ACK_DELAY_EXPONENT; + + tp->active_connection_id_limit = qcf->active_connection_id_limit; + tp->disable_active_migration = qcf->disable_active_migration; + + return NGX_OK; +} + + +ssize_t +ngx_quic_create_transport_params(u_char *pos, u_char *end, ngx_quic_tp_t *tp, + size_t *clen) +{ + u_char *p; + size_t len; + +#define ngx_quic_tp_len(id, value) \ + ngx_quic_varint_len(id) \ + + ngx_quic_varint_len(value) \ + + ngx_quic_varint_len(ngx_quic_varint_len(value)) + +#define ngx_quic_tp_vint(id, value) \ + do { \ + ngx_quic_build_int(&p, id); \ + ngx_quic_build_int(&p, ngx_quic_varint_len(value)); \ + ngx_quic_build_int(&p, value); \ + } while (0) + +#define ngx_quic_tp_strlen(id, value) \ + ngx_quic_varint_len(id) \ + + ngx_quic_varint_len(value.len) \ + + value.len + +#define ngx_quic_tp_str(id, value) \ + do { \ + ngx_quic_build_int(&p, id); \ + ngx_quic_build_int(&p, value.len); \ + p = ngx_cpymem(p, value.data, value.len); \ + } while (0) + + len = ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_DATA, tp->initial_max_data); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, + tp->initial_max_streams_uni); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, + tp->initial_max_streams_bidi); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + tp->initial_max_stream_data_bidi_local); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + tp->initial_max_stream_data_bidi_remote); + + len += ngx_quic_tp_len(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, + tp->initial_max_stream_data_uni); + + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, + tp->max_idle_timeout); + + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE, + tp->max_udp_payload_size); + + if (tp->disable_active_migration) { + len += ngx_quic_varint_len(NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); + len += ngx_quic_varint_len(0); + } + + len += ngx_quic_tp_len(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + tp->active_connection_id_limit); + + /* transport parameters listed above will be saved in 0-RTT context */ + if (clen) { + *clen = len; + } + + len += ngx_quic_tp_len(NGX_QUIC_TP_MAX_ACK_DELAY, + tp->max_ack_delay); + + len += ngx_quic_tp_len(NGX_QUIC_TP_ACK_DELAY_EXPONENT, + tp->ack_delay_exponent); + + len += ngx_quic_tp_strlen(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + len += ngx_quic_tp_strlen(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); + + if (tp->retry_scid.len) { + len += ngx_quic_tp_strlen(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); + } + + len += ngx_quic_varint_len(NGX_QUIC_TP_SR_TOKEN); + len += ngx_quic_varint_len(NGX_QUIC_SR_TOKEN_LEN); + len += NGX_QUIC_SR_TOKEN_LEN; + + if (pos == NULL) { + return len; + } + + p = pos; + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_DATA, + tp->initial_max_data); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI, + tp->initial_max_streams_uni); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI, + tp->initial_max_streams_bidi); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL, + tp->initial_max_stream_data_bidi_local); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE, + tp->initial_max_stream_data_bidi_remote); + + ngx_quic_tp_vint(NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI, + tp->initial_max_stream_data_uni); + + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_IDLE_TIMEOUT, + tp->max_idle_timeout); + + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE, + tp->max_udp_payload_size); + + if (tp->disable_active_migration) { + ngx_quic_build_int(&p, NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION); + ngx_quic_build_int(&p, 0); + } + + ngx_quic_tp_vint(NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT, + tp->active_connection_id_limit); + + ngx_quic_tp_vint(NGX_QUIC_TP_MAX_ACK_DELAY, + tp->max_ack_delay); + + ngx_quic_tp_vint(NGX_QUIC_TP_ACK_DELAY_EXPONENT, + tp->ack_delay_exponent); + + ngx_quic_tp_str(NGX_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + ngx_quic_tp_str(NGX_QUIC_TP_INITIAL_SCID, tp->initial_scid); + + if (tp->retry_scid.len) { + ngx_quic_tp_str(NGX_QUIC_TP_RETRY_SCID, tp->retry_scid); + } + + ngx_quic_build_int(&p, NGX_QUIC_TP_SR_TOKEN); + ngx_quic_build_int(&p, NGX_QUIC_SR_TOKEN_LEN); + p = ngx_cpymem(p, tp->sr_token, NGX_QUIC_SR_TOKEN_LEN); + + return p - pos; +} + + +static size_t +ngx_quic_create_close(u_char *p, ngx_quic_frame_t *f) +{ + size_t len; + u_char *start; + ngx_quic_close_frame_t *cl; + + cl = &f->u.close; + + if (p == NULL) { + len = ngx_quic_varint_len(f->type); + len += ngx_quic_varint_len(cl->error_code); + + if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) { + len += ngx_quic_varint_len(cl->frame_type); + } + + len += ngx_quic_varint_len(cl->reason.len); + len += cl->reason.len; + + return len; + } + + start = p; + + ngx_quic_build_int(&p, f->type); + ngx_quic_build_int(&p, cl->error_code); + + if (f->type != NGX_QUIC_FT_CONNECTION_CLOSE_APP) { + ngx_quic_build_int(&p, cl->frame_type); + } + + ngx_quic_build_int(&p, cl->reason.len); + p = ngx_cpymem(p, cl->reason.data, cl->reason.len); + + return p - start; +} + + +void +ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key) +{ + (void) ngx_quic_write_uint64(dcid, key); +} diff --git a/src/event/quic/ngx_event_quic_transport.h b/src/event/quic/ngx_event_quic_transport.h new file mode 100644 index 0000000..3e32039 --- /dev/null +++ b/src/event/quic/ngx_event_quic_transport.h @@ -0,0 +1,397 @@ + +/* + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ +#define _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +/* + * RFC 9000, 17.2. Long Header Packets + * 17.3. Short Header Packets + * + * QUIC flags in first byte + */ +#define NGX_QUIC_PKT_LONG 0x80 /* header form */ +#define NGX_QUIC_PKT_FIXED_BIT 0x40 +#define NGX_QUIC_PKT_TYPE 0x30 /* in long packet */ +#define NGX_QUIC_PKT_KPHASE 0x04 /* in short packet */ + +#define ngx_quic_long_pkt(flags) ((flags) & NGX_QUIC_PKT_LONG) +#define ngx_quic_short_pkt(flags) (((flags) & NGX_QUIC_PKT_LONG) == 0) + +/* Long packet types */ +#define NGX_QUIC_PKT_INITIAL 0x00 +#define NGX_QUIC_PKT_ZRTT 0x10 +#define NGX_QUIC_PKT_HANDSHAKE 0x20 +#define NGX_QUIC_PKT_RETRY 0x30 + +#define ngx_quic_pkt_in(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_INITIAL) +#define ngx_quic_pkt_zrtt(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_ZRTT) +#define ngx_quic_pkt_hs(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_HANDSHAKE) +#define ngx_quic_pkt_retry(flags) \ + (((flags) & NGX_QUIC_PKT_TYPE) == NGX_QUIC_PKT_RETRY) + +#define ngx_quic_pkt_rb_mask(flags) \ + (ngx_quic_long_pkt(flags) ? 0x0C : 0x18) +#define ngx_quic_pkt_hp_mask(flags) \ + (ngx_quic_long_pkt(flags) ? 0x0F : 0x1F) + +#define ngx_quic_level_name(lvl) \ + (lvl == ssl_encryption_application) ? "app" \ + : (lvl == ssl_encryption_initial) ? "init" \ + : (lvl == ssl_encryption_handshake) ? "hs" : "early" + +#define NGX_QUIC_MAX_CID_LEN 20 +#define NGX_QUIC_SERVER_CID_LEN NGX_QUIC_MAX_CID_LEN + +/* 12.4. Frames and Frame Types */ +#define NGX_QUIC_FT_PADDING 0x00 +#define NGX_QUIC_FT_PING 0x01 +#define NGX_QUIC_FT_ACK 0x02 +#define NGX_QUIC_FT_ACK_ECN 0x03 +#define NGX_QUIC_FT_RESET_STREAM 0x04 +#define NGX_QUIC_FT_STOP_SENDING 0x05 +#define NGX_QUIC_FT_CRYPTO 0x06 +#define NGX_QUIC_FT_NEW_TOKEN 0x07 +#define NGX_QUIC_FT_STREAM 0x08 +#define NGX_QUIC_FT_STREAM1 0x09 +#define NGX_QUIC_FT_STREAM2 0x0A +#define NGX_QUIC_FT_STREAM3 0x0B +#define NGX_QUIC_FT_STREAM4 0x0C +#define NGX_QUIC_FT_STREAM5 0x0D +#define NGX_QUIC_FT_STREAM6 0x0E +#define NGX_QUIC_FT_STREAM7 0x0F +#define NGX_QUIC_FT_MAX_DATA 0x10 +#define NGX_QUIC_FT_MAX_STREAM_DATA 0x11 +#define NGX_QUIC_FT_MAX_STREAMS 0x12 +#define NGX_QUIC_FT_MAX_STREAMS2 0x13 +#define NGX_QUIC_FT_DATA_BLOCKED 0x14 +#define NGX_QUIC_FT_STREAM_DATA_BLOCKED 0x15 +#define NGX_QUIC_FT_STREAMS_BLOCKED 0x16 +#define NGX_QUIC_FT_STREAMS_BLOCKED2 0x17 +#define NGX_QUIC_FT_NEW_CONNECTION_ID 0x18 +#define NGX_QUIC_FT_RETIRE_CONNECTION_ID 0x19 +#define NGX_QUIC_FT_PATH_CHALLENGE 0x1A +#define NGX_QUIC_FT_PATH_RESPONSE 0x1B +#define NGX_QUIC_FT_CONNECTION_CLOSE 0x1C +#define NGX_QUIC_FT_CONNECTION_CLOSE_APP 0x1D +#define NGX_QUIC_FT_HANDSHAKE_DONE 0x1E + +#define NGX_QUIC_FT_LAST NGX_QUIC_FT_HANDSHAKE_DONE + +/* 22.5. QUIC Transport Error Codes Registry */ +#define NGX_QUIC_ERR_NO_ERROR 0x00 +#define NGX_QUIC_ERR_INTERNAL_ERROR 0x01 +#define NGX_QUIC_ERR_CONNECTION_REFUSED 0x02 +#define NGX_QUIC_ERR_FLOW_CONTROL_ERROR 0x03 +#define NGX_QUIC_ERR_STREAM_LIMIT_ERROR 0x04 +#define NGX_QUIC_ERR_STREAM_STATE_ERROR 0x05 +#define NGX_QUIC_ERR_FINAL_SIZE_ERROR 0x06 +#define NGX_QUIC_ERR_FRAME_ENCODING_ERROR 0x07 +#define NGX_QUIC_ERR_TRANSPORT_PARAMETER_ERROR 0x08 +#define NGX_QUIC_ERR_CONNECTION_ID_LIMIT_ERROR 0x09 +#define NGX_QUIC_ERR_PROTOCOL_VIOLATION 0x0A +#define NGX_QUIC_ERR_INVALID_TOKEN 0x0B +#define NGX_QUIC_ERR_APPLICATION_ERROR 0x0C +#define NGX_QUIC_ERR_CRYPTO_BUFFER_EXCEEDED 0x0D +#define NGX_QUIC_ERR_KEY_UPDATE_ERROR 0x0E +#define NGX_QUIC_ERR_AEAD_LIMIT_REACHED 0x0F +#define NGX_QUIC_ERR_NO_VIABLE_PATH 0x10 + +#define NGX_QUIC_ERR_CRYPTO_ERROR 0x100 + +#define NGX_QUIC_ERR_CRYPTO(e) (NGX_QUIC_ERR_CRYPTO_ERROR + (e)) + + +/* 22.3. QUIC Transport Parameters Registry */ +#define NGX_QUIC_TP_ORIGINAL_DCID 0x00 +#define NGX_QUIC_TP_MAX_IDLE_TIMEOUT 0x01 +#define NGX_QUIC_TP_SR_TOKEN 0x02 +#define NGX_QUIC_TP_MAX_UDP_PAYLOAD_SIZE 0x03 +#define NGX_QUIC_TP_INITIAL_MAX_DATA 0x04 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_LOCAL 0x05 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_BIDI_REMOTE 0x06 +#define NGX_QUIC_TP_INITIAL_MAX_STREAM_DATA_UNI 0x07 +#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_BIDI 0x08 +#define NGX_QUIC_TP_INITIAL_MAX_STREAMS_UNI 0x09 +#define NGX_QUIC_TP_ACK_DELAY_EXPONENT 0x0A +#define NGX_QUIC_TP_MAX_ACK_DELAY 0x0B +#define NGX_QUIC_TP_DISABLE_ACTIVE_MIGRATION 0x0C +#define NGX_QUIC_TP_PREFERRED_ADDRESS 0x0D +#define NGX_QUIC_TP_ACTIVE_CONNECTION_ID_LIMIT 0x0E +#define NGX_QUIC_TP_INITIAL_SCID 0x0F +#define NGX_QUIC_TP_RETRY_SCID 0x10 + +#define NGX_QUIC_CID_LEN_MIN 8 +#define NGX_QUIC_CID_LEN_MAX 20 + +#define NGX_QUIC_MAX_RANGES 10 + + +typedef struct { + uint64_t gap; + uint64_t range; +} ngx_quic_ack_range_t; + + +typedef struct { + uint64_t largest; + uint64_t delay; + uint64_t range_count; + uint64_t first_range; + uint64_t ect0; + uint64_t ect1; + uint64_t ce; + uint64_t ranges_length; +} ngx_quic_ack_frame_t; + + +typedef struct { + uint64_t seqnum; + uint64_t retire; + uint8_t len; + u_char cid[NGX_QUIC_CID_LEN_MAX]; + u_char srt[NGX_QUIC_SR_TOKEN_LEN]; +} ngx_quic_new_conn_id_frame_t; + + +typedef struct { + uint64_t length; +} ngx_quic_new_token_frame_t; + +/* + * common layout for CRYPTO and STREAM frames; + * conceptually, CRYPTO frame is also a stream + * frame lacking some properties + */ +typedef struct { + uint64_t offset; + uint64_t length; +} ngx_quic_ordered_frame_t; + +typedef ngx_quic_ordered_frame_t ngx_quic_crypto_frame_t; + + +typedef struct { + /* initial fields same as in ngx_quic_ordered_frame_t */ + uint64_t offset; + uint64_t length; + + uint64_t stream_id; + unsigned off:1; + unsigned len:1; + unsigned fin:1; +} ngx_quic_stream_frame_t; + + +typedef struct { + uint64_t max_data; +} ngx_quic_max_data_frame_t; + + +typedef struct { + uint64_t error_code; + uint64_t frame_type; + ngx_str_t reason; +} ngx_quic_close_frame_t; + + +typedef struct { + uint64_t id; + uint64_t error_code; + uint64_t final_size; +} ngx_quic_reset_stream_frame_t; + + +typedef struct { + uint64_t id; + uint64_t error_code; +} ngx_quic_stop_sending_frame_t; + + +typedef struct { + uint64_t limit; + ngx_uint_t bidi; /* unsigned: bidi:1 */ +} ngx_quic_streams_blocked_frame_t; + + +typedef struct { + uint64_t limit; + ngx_uint_t bidi; /* unsigned: bidi:1 */ +} ngx_quic_max_streams_frame_t; + + +typedef struct { + uint64_t id; + uint64_t limit; +} ngx_quic_max_stream_data_frame_t; + + +typedef struct { + uint64_t limit; +} ngx_quic_data_blocked_frame_t; + + +typedef struct { + uint64_t id; + uint64_t limit; +} ngx_quic_stream_data_blocked_frame_t; + + +typedef struct { + uint64_t sequence_number; +} ngx_quic_retire_cid_frame_t; + + +typedef struct { + u_char data[8]; +} ngx_quic_path_challenge_frame_t; + + +typedef struct ngx_quic_frame_s ngx_quic_frame_t; + +struct ngx_quic_frame_s { + ngx_uint_t type; + enum ssl_encryption_level_t level; + ngx_queue_t queue; + uint64_t pnum; + size_t plen; + ngx_msec_t send_time; + ssize_t len; + unsigned need_ack:1; + unsigned pkt_need_ack:1; + unsigned ignore_congestion:1; + + ngx_chain_t *data; + union { + ngx_quic_ack_frame_t ack; + ngx_quic_crypto_frame_t crypto; + ngx_quic_ordered_frame_t ord; + ngx_quic_new_conn_id_frame_t ncid; + ngx_quic_new_token_frame_t token; + ngx_quic_stream_frame_t stream; + ngx_quic_max_data_frame_t max_data; + ngx_quic_close_frame_t close; + ngx_quic_reset_stream_frame_t reset_stream; + ngx_quic_stop_sending_frame_t stop_sending; + ngx_quic_streams_blocked_frame_t streams_blocked; + ngx_quic_max_streams_frame_t max_streams; + ngx_quic_max_stream_data_frame_t max_stream_data; + ngx_quic_data_blocked_frame_t data_blocked; + ngx_quic_stream_data_blocked_frame_t stream_data_blocked; + ngx_quic_retire_cid_frame_t retire_cid; + ngx_quic_path_challenge_frame_t path_challenge; + ngx_quic_path_challenge_frame_t path_response; + } u; +}; + + +typedef struct { + ngx_log_t *log; + ngx_quic_path_t *path; + + ngx_quic_keys_t *keys; + + ngx_msec_t received; + uint64_t number; + uint8_t num_len; + uint32_t trunc; + uint8_t flags; + uint32_t version; + ngx_str_t token; + enum ssl_encryption_level_t level; + ngx_uint_t error; + + /* filled in by parser */ + ngx_buf_t *raw; /* udp datagram */ + + u_char *data; /* quic packet */ + size_t len; + + /* cleartext fields */ + ngx_str_t odcid; /* retry packet tag */ + u_char odcid_buf[NGX_QUIC_MAX_CID_LEN]; + ngx_str_t dcid; + ngx_str_t scid; + uint64_t pn; + u_char *plaintext; + ngx_str_t payload; /* decrypted data */ + + unsigned need_ack:1; + unsigned key_phase:1; + unsigned key_update:1; + unsigned parsed:1; + unsigned decrypted:1; + unsigned validated:1; + unsigned retried:1; + unsigned first:1; + unsigned rebound:1; + unsigned path_challenged:1; +} ngx_quic_header_t; + + +typedef struct { + ngx_msec_t max_idle_timeout; + ngx_msec_t max_ack_delay; + + size_t max_udp_payload_size; + size_t initial_max_data; + size_t initial_max_stream_data_bidi_local; + size_t initial_max_stream_data_bidi_remote; + size_t initial_max_stream_data_uni; + ngx_uint_t initial_max_streams_bidi; + ngx_uint_t initial_max_streams_uni; + ngx_uint_t ack_delay_exponent; + ngx_uint_t active_connection_id_limit; + ngx_flag_t disable_active_migration; + + ngx_str_t original_dcid; + ngx_str_t initial_scid; + ngx_str_t retry_scid; + u_char sr_token[NGX_QUIC_SR_TOKEN_LEN]; + + /* TODO */ + void *preferred_address; +} ngx_quic_tp_t; + + +ngx_int_t ngx_quic_parse_packet(ngx_quic_header_t *pkt); + +size_t ngx_quic_create_version_negotiation(ngx_quic_header_t *pkt, u_char *out); + +size_t ngx_quic_payload_size(ngx_quic_header_t *pkt, size_t pkt_len); + +size_t ngx_quic_create_header(ngx_quic_header_t *pkt, u_char *out, + u_char **pnp); + +size_t ngx_quic_create_retry_itag(ngx_quic_header_t *pkt, u_char *out, + u_char **start); + +ssize_t ngx_quic_parse_frame(ngx_quic_header_t *pkt, u_char *start, u_char *end, + ngx_quic_frame_t *frame); +ssize_t ngx_quic_create_frame(u_char *p, ngx_quic_frame_t *f); + +ssize_t ngx_quic_parse_ack_range(ngx_log_t *log, u_char *start, + u_char *end, uint64_t *gap, uint64_t *range); +size_t ngx_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range); + +ngx_int_t ngx_quic_init_transport_params(ngx_quic_tp_t *tp, + ngx_quic_conf_t *qcf); +ngx_int_t ngx_quic_parse_transport_params(u_char *p, u_char *end, + ngx_quic_tp_t *tp, ngx_log_t *log); +ssize_t ngx_quic_create_transport_params(u_char *p, u_char *end, + ngx_quic_tp_t *tp, size_t *clen); + +void ngx_quic_dcid_encode_key(u_char *dcid, uint64_t key); + +#endif /* _NGX_EVENT_QUIC_TRANSPORT_H_INCLUDED_ */ diff --git a/src/event/quic/ngx_event_quic_udp.c b/src/event/quic/ngx_event_quic_udp.c new file mode 100644 index 0000000..15b54bc --- /dev/null +++ b/src/event/quic/ngx_event_quic_udp.c @@ -0,0 +1,420 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <ngx_event_quic_connection.h> + + +static void ngx_quic_close_accepted_connection(ngx_connection_t *c); +static ngx_connection_t *ngx_quic_lookup_connection(ngx_listening_t *ls, + ngx_str_t *key, struct sockaddr *local_sockaddr, socklen_t local_socklen); + + +void +ngx_quic_recvmsg(ngx_event_t *ev) +{ + ssize_t n; + ngx_str_t key; + ngx_buf_t buf; + ngx_log_t *log; + ngx_err_t err; + socklen_t socklen, local_socklen; + ngx_event_t *rev, *wev; + struct iovec iov[1]; + struct msghdr msg; + ngx_sockaddr_t sa, lsa; + struct sockaddr *sockaddr, *local_sockaddr; + ngx_listening_t *ls; + ngx_event_conf_t *ecf; + ngx_connection_t *c, *lc; + ngx_quic_socket_t *qsock; + static u_char buffer[NGX_QUIC_MAX_UDP_PAYLOAD_SIZE]; + +#if (NGX_HAVE_ADDRINFO_CMSG) + u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; +#endif + + if (ev->timedout) { + if (ngx_enable_accept_events((ngx_cycle_t *) ngx_cycle) != NGX_OK) { + return; + } + + ev->timedout = 0; + } + + ecf = ngx_event_get_conf(ngx_cycle->conf_ctx, ngx_event_core_module); + + if (!(ngx_event_flags & NGX_USE_KQUEUE_EVENT)) { + ev->available = ecf->multi_accept; + } + + lc = ev->data; + ls = lc->listening; + ev->ready = 0; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, ev->log, 0, + "quic recvmsg on %V, ready: %d", + &ls->addr_text, ev->available); + + do { + ngx_memzero(&msg, sizeof(struct msghdr)); + + iov[0].iov_base = (void *) buffer; + iov[0].iov_len = sizeof(buffer); + + msg.msg_name = &sa; + msg.msg_namelen = sizeof(ngx_sockaddr_t); + msg.msg_iov = iov; + msg.msg_iovlen = 1; + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (ls->wildcard) { + msg.msg_control = &msg_control; + msg.msg_controllen = sizeof(msg_control); + + ngx_memzero(&msg_control, sizeof(msg_control)); + } +#endif + + n = recvmsg(lc->fd, &msg, 0); + + if (n == -1) { + err = ngx_socket_errno; + + if (err == NGX_EAGAIN) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ev->log, err, + "quic recvmsg() not ready"); + return; + } + + ngx_log_error(NGX_LOG_ALERT, ev->log, err, "quic recvmsg() failed"); + + return; + } + +#if (NGX_HAVE_ADDRINFO_CMSG) + if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { + ngx_log_error(NGX_LOG_ALERT, ev->log, 0, + "quic recvmsg() truncated data"); + continue; + } +#endif + + sockaddr = msg.msg_name; + socklen = msg.msg_namelen; + + if (socklen > (socklen_t) sizeof(ngx_sockaddr_t)) { + socklen = sizeof(ngx_sockaddr_t); + } + +#if (NGX_HAVE_UNIX_DOMAIN) + + if (sockaddr->sa_family == AF_UNIX) { + struct sockaddr_un *saun = (struct sockaddr_un *) sockaddr; + + if (socklen <= (socklen_t) offsetof(struct sockaddr_un, sun_path) + || saun->sun_path[0] == '\0') + { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, ngx_cycle->log, 0, + "unbound unix socket"); + goto next; + } + } + +#endif + + local_sockaddr = ls->sockaddr; + local_socklen = ls->socklen; + +#if (NGX_HAVE_ADDRINFO_CMSG) + + if (ls->wildcard) { + struct cmsghdr *cmsg; + + ngx_memcpy(&lsa, local_sockaddr, local_socklen); + local_sockaddr = &lsa.sockaddr; + + for (cmsg = CMSG_FIRSTHDR(&msg); + cmsg != NULL; + cmsg = CMSG_NXTHDR(&msg, cmsg)) + { + if (ngx_get_srcaddr_cmsg(cmsg, local_sockaddr) == NGX_OK) { + break; + } + } + } + +#endif + + if (ngx_quic_get_packet_dcid(ev->log, buffer, n, &key) != NGX_OK) { + goto next; + } + + c = ngx_quic_lookup_connection(ls, &key, local_sockaddr, local_socklen); + + if (c) { + +#if (NGX_DEBUG) + if (c->log->log_level & NGX_LOG_DEBUG_EVENT) { + ngx_log_handler_pt handler; + + handler = c->log->handler; + c->log->handler = NULL; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "quic recvmsg: fd:%d n:%z", c->fd, n); + + c->log->handler = handler; + } +#endif + + ngx_memzero(&buf, sizeof(ngx_buf_t)); + + buf.pos = buffer; + buf.last = buffer + n; + buf.start = buf.pos; + buf.end = buffer + sizeof(buffer); + + qsock = ngx_quic_get_socket(c); + + ngx_memcpy(&qsock->sockaddr, sockaddr, socklen); + qsock->socklen = socklen; + + c->udp->buffer = &buf; + + rev = c->read; + rev->ready = 1; + rev->active = 0; + + rev->handler(rev); + + if (c->udp) { + c->udp->buffer = NULL; + } + + rev->ready = 0; + rev->active = 1; + + goto next; + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_accepted, 1); +#endif + + ngx_accept_disabled = ngx_cycle->connection_n / 8 + - ngx_cycle->free_connection_n; + + c = ngx_get_connection(lc->fd, ev->log); + if (c == NULL) { + return; + } + + c->shared = 1; + c->type = SOCK_DGRAM; + c->socklen = socklen; + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + + c->pool = ngx_create_pool(ls->pool_size, ev->log); + if (c->pool == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + c->sockaddr = ngx_palloc(c->pool, NGX_SOCKADDRLEN); + if (c->sockaddr == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + ngx_memcpy(c->sockaddr, sockaddr, socklen); + + log = ngx_palloc(c->pool, sizeof(ngx_log_t)); + if (log == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + *log = ls->log; + + c->log = log; + c->pool->log = log; + c->listening = ls; + + if (local_sockaddr == &lsa.sockaddr) { + local_sockaddr = ngx_palloc(c->pool, local_socklen); + if (local_sockaddr == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + ngx_memcpy(local_sockaddr, &lsa, local_socklen); + } + + c->local_sockaddr = local_sockaddr; + c->local_socklen = local_socklen; + + c->buffer = ngx_create_temp_buf(c->pool, n); + if (c->buffer == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + c->buffer->last = ngx_cpymem(c->buffer->last, buffer, n); + + rev = c->read; + wev = c->write; + + rev->active = 1; + wev->ready = 1; + + rev->log = log; + wev->log = log; + + /* + * TODO: MT: - ngx_atomic_fetch_add() + * or protection by critical section or light mutex + * + * TODO: MP: - allocated in a shared memory + * - ngx_atomic_fetch_add() + * or protection by critical section or light mutex + */ + + c->number = ngx_atomic_fetch_add(ngx_connection_counter, 1); + + c->start_time = ngx_current_msec; + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_handled, 1); +#endif + + if (ls->addr_ntop) { + c->addr_text.data = ngx_pnalloc(c->pool, ls->addr_text_max_len); + if (c->addr_text.data == NULL) { + ngx_quic_close_accepted_connection(c); + return; + } + + c->addr_text.len = ngx_sock_ntop(c->sockaddr, c->socklen, + c->addr_text.data, + ls->addr_text_max_len, 0); + if (c->addr_text.len == 0) { + ngx_quic_close_accepted_connection(c); + return; + } + } + +#if (NGX_DEBUG) + { + ngx_str_t addr; + u_char text[NGX_SOCKADDR_STRLEN]; + + ngx_debug_accepted_connection(ecf, c); + + if (log->log_level & NGX_LOG_DEBUG_EVENT) { + addr.data = text; + addr.len = ngx_sock_ntop(c->sockaddr, c->socklen, text, + NGX_SOCKADDR_STRLEN, 1); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, log, 0, + "*%uA quic recvmsg: %V fd:%d n:%z", + c->number, &addr, c->fd, n); + } + + } +#endif + + log->data = NULL; + log->handler = NULL; + + ls->handler(c); + + next: + + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + ev->available -= n; + } + + } while (ev->available); +} + + +static void +ngx_quic_close_accepted_connection(ngx_connection_t *c) +{ + ngx_free_connection(c); + + c->fd = (ngx_socket_t) -1; + + if (c->pool) { + ngx_destroy_pool(c->pool); + } + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, -1); +#endif +} + + +static ngx_connection_t * +ngx_quic_lookup_connection(ngx_listening_t *ls, ngx_str_t *key, + struct sockaddr *local_sockaddr, socklen_t local_socklen) +{ + uint32_t hash; + ngx_int_t rc; + ngx_connection_t *c; + ngx_rbtree_node_t *node, *sentinel; + ngx_quic_socket_t *qsock; + + if (key->len == 0) { + return NULL; + } + + node = ls->rbtree.root; + sentinel = ls->rbtree.sentinel; + hash = ngx_crc32_long(key->data, key->len); + + while (node != sentinel) { + + if (hash < node->key) { + node = node->left; + continue; + } + + if (hash > node->key) { + node = node->right; + continue; + } + + /* hash == node->key */ + + qsock = (ngx_quic_socket_t *) node; + + rc = ngx_memn2cmp(key->data, qsock->sid.id, key->len, qsock->sid.len); + + c = qsock->udp.connection; + + if (rc == 0 && ls->wildcard) { + rc = ngx_cmp_sockaddr(local_sockaddr, local_socklen, + c->local_sockaddr, c->local_socklen, 1); + } + + if (rc == 0) { + c->udp = &qsock->udp; + return c; + } + + node = (rc < 0) ? node->left : node->right; + } + + return NULL; +} diff --git a/src/http/modules/ngx_http_access_module.c b/src/http/modules/ngx_http_access_module.c index 7355de9..ea75520 100644 --- a/src/http/modules/ngx_http_access_module.c +++ b/src/http/modules/ngx_http_access_module.c @@ -148,7 +148,7 @@ ngx_http_access_handler(ngx_http_request_t *r) p = sin6->sin6_addr.s6_addr; if (alcf->rules && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { - addr = p[12] << 24; + addr = (in_addr_t) p[12] << 24; addr += p[13] << 16; addr += p[14] << 8; addr += p[15]; diff --git a/src/http/modules/ngx_http_auth_basic_module.c b/src/http/modules/ngx_http_auth_basic_module.c index 0693319..02d41e8 100644 --- a/src/http/modules/ngx_http_auth_basic_module.c +++ b/src/http/modules/ngx_http_auth_basic_module.c @@ -339,6 +339,7 @@ ngx_http_auth_basic_set_realm(ngx_http_request_t *r, ngx_str_t *realm) *p = '"'; r->headers_out.www_authenticate->hash = 1; + r->headers_out.www_authenticate->next = NULL; ngx_str_set(&r->headers_out.www_authenticate->key, "WWW-Authenticate"); r->headers_out.www_authenticate->value.data = basic; r->headers_out.www_authenticate->value.len = len; diff --git a/src/http/modules/ngx_http_auth_request_module.c b/src/http/modules/ngx_http_auth_request_module.c index bab79e4..8bc98aa 100644 --- a/src/http/modules/ngx_http_auth_request_module.c +++ b/src/http/modules/ngx_http_auth_request_module.c @@ -101,7 +101,7 @@ ngx_module_t ngx_http_auth_request_module = { static ngx_int_t ngx_http_auth_request_handler(ngx_http_request_t *r) { - ngx_table_elt_t *h, *ho; + ngx_table_elt_t *h, *ho, **ph; ngx_http_request_t *sr; ngx_http_post_subrequest_t *ps; ngx_http_auth_request_ctx_t *ctx; @@ -147,15 +147,21 @@ ngx_http_auth_request_handler(ngx_http_request_t *r) h = sr->upstream->headers_in.www_authenticate; } - if (h) { + ph = &r->headers_out.www_authenticate; + + while (h) { ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; } *ho = *h; + ho->next = NULL; + + *ph = ho; + ph = &ho->next; - r->headers_out.www_authenticate = ho; + h = h->next; } return ctx->status; diff --git a/src/http/modules/ngx_http_dav_module.c b/src/http/modules/ngx_http_dav_module.c index 0cc9ae1..cfb9892 100644 --- a/src/http/modules/ngx_http_dav_module.c +++ b/src/http/modules/ngx_http_dav_module.c @@ -1082,6 +1082,7 @@ ngx_http_dav_location(ngx_http_request_t *r) } r->headers_out.location->hash = 1; + r->headers_out.location->next = NULL; ngx_str_set(&r->headers_out.location->key, "Location"); escape = 2 * ngx_escape_uri(NULL, r->uri.data, r->uri.len, NGX_ESCAPE_URI); diff --git a/src/http/modules/ngx_http_fastcgi_module.c b/src/http/modules/ngx_http_fastcgi_module.c index 4a8dc33..b989083 100644 --- a/src/http/modules/ngx_http_fastcgi_module.c +++ b/src/http/modules/ngx_http_fastcgi_module.c @@ -835,14 +835,14 @@ static ngx_int_t ngx_http_fastcgi_create_request(ngx_http_request_t *r) { off_t file_pos; - u_char ch, *pos, *lowcase_key; + u_char ch, sep, *pos, *lowcase_key; size_t size, len, key_len, val_len, padding, allocated; ngx_uint_t i, n, next, hash, skip_empty, header_params; ngx_buf_t *b; ngx_chain_t *cl, *body; ngx_list_part_t *part; - ngx_table_elt_t *header, **ignored; + ngx_table_elt_t *header, *hn, **ignored; ngx_http_upstream_t *u; ngx_http_script_code_pt code; ngx_http_script_engine_t e, le; @@ -900,7 +900,11 @@ ngx_http_fastcgi_create_request(ngx_http_request_t *r) allocated = 0; lowcase_key = NULL; - if (params->number) { + if (ngx_http_link_multi_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + if (params->number || r->headers_in.multi) { n = 0; part = &r->headers_in.headers.part; @@ -930,6 +934,12 @@ ngx_http_fastcgi_create_request(ngx_http_request_t *r) i = 0; } + for (n = 0; n < header_params; n++) { + if (&header[i] == ignored[n]) { + goto next_length; + } + } + if (params->number) { if (allocated < header[i].key.len) { allocated = header[i].key.len + 16; @@ -959,15 +969,23 @@ ngx_http_fastcgi_create_request(ngx_http_request_t *r) ignored[header_params++] = &header[i]; continue; } + } - n += sizeof("HTTP_") - 1; + key_len = sizeof("HTTP_") - 1 + header[i].key.len; - } else { - n = sizeof("HTTP_") - 1 + header[i].key.len; + val_len = header[i].value.len; + + for (hn = header[i].next; hn; hn = hn->next) { + val_len += hn->value.len + 2; + ignored[header_params++] = hn; } - len += ((n > 127) ? 4 : 1) + ((header[i].value.len > 127) ? 4 : 1) - + n + header[i].value.len; + len += ((key_len > 127) ? 4 : 1) + key_len + + ((val_len > 127) ? 4 : 1) + val_len; + + next_length: + + continue; } } @@ -1109,7 +1127,7 @@ ngx_http_fastcgi_create_request(ngx_http_request_t *r) for (n = 0; n < header_params; n++) { if (&header[i] == ignored[n]) { - goto next; + goto next_value; } } @@ -1125,6 +1143,11 @@ ngx_http_fastcgi_create_request(ngx_http_request_t *r) } val_len = header[i].value.len; + + for (hn = header[i].next; hn; hn = hn->next) { + val_len += hn->value.len + 2; + } + if (val_len > 127) { *b->last++ = (u_char) (((val_len >> 24) & 0x7f) | 0x80); *b->last++ = (u_char) ((val_len >> 16) & 0xff); @@ -1150,13 +1173,34 @@ ngx_http_fastcgi_create_request(ngx_http_request_t *r) *b->last++ = ch; } - b->last = ngx_copy(b->last, header[i].value.data, val_len); + b->last = ngx_copy(b->last, header[i].value.data, + header[i].value.len); + + if (header[i].next) { + + if (header[i].key.len == sizeof("Cookie") - 1 + && ngx_strncasecmp(header[i].key.data, (u_char *) "Cookie", + sizeof("Cookie") - 1) + == 0) + { + sep = ';'; + + } else { + sep = ','; + } + + for (hn = header[i].next; hn; hn = hn->next) { + *b->last++ = sep; + *b->last++ = ' '; + b->last = ngx_copy(b->last, hn->value.data, hn->value.len); + } + } ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "fastcgi param: \"%*s: %*s\"", key_len, b->last - (key_len + val_len), val_len, b->last - val_len); - next: + next_value: continue; } @@ -1963,8 +2007,12 @@ ngx_http_fastcgi_process_header(ngx_http_request_t *r) hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return NGX_ERROR; + if (hh) { + rc = hh->handler(r, h, hh->offset); + + if (rc != NGX_OK) { + return rc; + } } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -2000,7 +2048,10 @@ ngx_http_fastcgi_process_header(ngx_http_request_t *r) } u->headers_in.status_n = status; - u->headers_in.status_line = *status_line; + + if (status_line->len > 3) { + u->headers_in.status_line = *status_line; + } } else if (u->headers_in.location) { u->headers_in.status_n = 302; diff --git a/src/http/modules/ngx_http_flv_module.c b/src/http/modules/ngx_http_flv_module.c index cc06d53..ef2bff4 100644 --- a/src/http/modules/ngx_http_flv_module.c +++ b/src/http/modules/ngx_http_flv_module.c @@ -232,9 +232,10 @@ ngx_http_flv_handler(ngx_http_request_t *r) b->file_pos = start; b->file_last = of.size; - b->in_file = b->file_last ? 1: 0; + b->in_file = b->file_last ? 1 : 0; b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; + b->sync = (b->last_buf || b->in_file) ? 0 : 1; b->file->fd = of.fd; b->file->name = path; diff --git a/src/http/modules/ngx_http_geo_module.c b/src/http/modules/ngx_http_geo_module.c index 153b6aa..75c0397 100644 --- a/src/http/modules/ngx_http_geo_module.c +++ b/src/http/modules/ngx_http_geo_module.c @@ -199,7 +199,7 @@ ngx_http_geo_cidr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, p = inaddr6->s6_addr; if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; @@ -272,7 +272,7 @@ ngx_http_geo_range_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { p = inaddr6->s6_addr; - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; @@ -327,15 +327,15 @@ static ngx_int_t ngx_http_geo_addr(ngx_http_request_t *r, ngx_http_geo_ctx_t *ctx, ngx_addr_t *addr) { - ngx_array_t *xfwd; + ngx_table_elt_t *xfwd; if (ngx_http_geo_real_addr(r, ctx, addr) != NGX_OK) { return NGX_ERROR; } - xfwd = &r->headers_in.x_forwarded_for; + xfwd = r->headers_in.x_forwarded_for; - if (xfwd->nelts > 0 && ctx->proxies != NULL) { + if (xfwd != NULL && ctx->proxies != NULL) { (void) ngx_http_get_forwarded_addr(r, addr, xfwd, NULL, ctx->proxies, ctx->proxy_recursive); } @@ -1259,7 +1259,7 @@ ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, return gvvn->value; } - val = ngx_palloc(ctx->pool, sizeof(ngx_http_variable_value_t)); + val = ngx_pcalloc(ctx->pool, sizeof(ngx_http_variable_value_t)); if (val == NULL) { return NULL; } @@ -1271,8 +1271,6 @@ ngx_http_geo_value(ngx_conf_t *cf, ngx_http_geo_conf_ctx_t *ctx, } val->valid = 1; - val->no_cacheable = 0; - val->not_found = 0; gvvn = ngx_palloc(ctx->temp_pool, sizeof(ngx_http_geo_variable_value_node_t)); diff --git a/src/http/modules/ngx_http_geoip_module.c b/src/http/modules/ngx_http_geoip_module.c index 5ea4f5f..5b8b11f 100644 --- a/src/http/modules/ngx_http_geoip_module.c +++ b/src/http/modules/ngx_http_geoip_module.c @@ -240,16 +240,16 @@ static u_long ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) { ngx_addr_t addr; - ngx_array_t *xfwd; + ngx_table_elt_t *xfwd; struct sockaddr_in *sin; addr.sockaddr = r->connection->sockaddr; addr.socklen = r->connection->socklen; /* addr.name = r->connection->addr_text; */ - xfwd = &r->headers_in.x_forwarded_for; + xfwd = r->headers_in.x_forwarded_for; - if (xfwd->nelts > 0 && gcf->proxies != NULL) { + if (xfwd != NULL && gcf->proxies != NULL) { (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL, gcf->proxies, gcf->proxy_recursive); } @@ -266,7 +266,7 @@ ngx_http_geoip_addr(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { p = inaddr6->s6_addr; - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; @@ -292,7 +292,7 @@ static geoipv6_t ngx_http_geoip_addr_v6(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) { ngx_addr_t addr; - ngx_array_t *xfwd; + ngx_table_elt_t *xfwd; in_addr_t addr4; struct in6_addr addr6; struct sockaddr_in *sin; @@ -302,9 +302,9 @@ ngx_http_geoip_addr_v6(ngx_http_request_t *r, ngx_http_geoip_conf_t *gcf) addr.socklen = r->connection->socklen; /* addr.name = r->connection->addr_text; */ - xfwd = &r->headers_in.x_forwarded_for; + xfwd = r->headers_in.x_forwarded_for; - if (xfwd->nelts > 0 && gcf->proxies != NULL) { + if (xfwd != NULL && gcf->proxies != NULL) { (void) ngx_http_get_forwarded_addr(r, &addr, xfwd, NULL, gcf->proxies, gcf->proxy_recursive); } diff --git a/src/http/modules/ngx_http_grpc_module.c b/src/http/modules/ngx_http_grpc_module.c index 864fc4f..e7726f3 100644 --- a/src/http/modules/ngx_http_grpc_module.c +++ b/src/http/modules/ngx_http_grpc_module.c @@ -209,6 +209,8 @@ static char *ngx_http_grpc_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_grpc_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data); +static ngx_int_t ngx_http_grpc_merge_ssl(ngx_conf_t *cf, + ngx_http_grpc_loc_conf_t *conf, ngx_http_grpc_loc_conf_t *prev); static ngx_int_t ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf); #endif @@ -562,7 +564,7 @@ ngx_http_grpc_handler(ngx_http_request_t *r) ctx->host = glcf->host; #if (NGX_HTTP_SSL) - u->ssl = (glcf->upstream.ssl != NULL); + u->ssl = glcf->ssl; if (u->ssl) { ngx_str_set(&u->schema, "grpcs://"); @@ -1229,7 +1231,7 @@ ngx_http_grpc_body_output_filter(void *data, ngx_chain_t *in) ngx_buf_t *b; ngx_int_t rc; ngx_uint_t next, last; - ngx_chain_t *cl, *out, **ll; + ngx_chain_t *cl, *out, *ln, **ll; ngx_http_upstream_t *u; ngx_http_grpc_ctx_t *ctx; ngx_http_grpc_frame_t *f; @@ -1457,7 +1459,10 @@ ngx_http_grpc_body_output_filter(void *data, ngx_chain_t *in) last = 1; } + ln = in; in = in->next; + + ngx_free_chain(r->pool, ln); } ctx->in = in; @@ -1891,8 +1896,12 @@ ngx_http_grpc_process_header(ngx_http_request_t *r) hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return NGX_ERROR; + if (hh) { + rc = hh->handler(r, h, hh->offset); + + if (rc != NGX_OK) { + return rc; + } } continue; @@ -4459,12 +4468,17 @@ ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) #if (NGX_HTTP_SSL) + if (ngx_http_grpc_merge_ssl(cf, conf, prev) != NGX_OK) { + return NGX_CONF_ERROR; + } + ngx_conf_merge_value(conf->upstream.ssl_session_reuse, prev->upstream.ssl_session_reuse, 1); ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols, - (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 - |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); + (NGX_CONF_BITMASK_SET + |NGX_SSL_TLSv1|NGX_SSL_TLSv1_1 + |NGX_SSL_TLSv1_2|NGX_SSL_TLSv1_3)); ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT"); @@ -4520,7 +4534,7 @@ ngx_http_grpc_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) conf->grpc_values = prev->grpc_values; #if (NGX_HTTP_SSL) - conf->upstream.ssl = prev->upstream.ssl; + conf->ssl = prev->ssl; #endif } @@ -4870,16 +4884,62 @@ ngx_http_grpc_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) static ngx_int_t -ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf) +ngx_http_grpc_merge_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *conf, + ngx_http_grpc_loc_conf_t *prev) { - ngx_pool_cleanup_t *cln; + ngx_uint_t preserve; + + if (conf->ssl_protocols == 0 + && conf->ssl_ciphers.data == NULL + && conf->upstream.ssl_certificate == NGX_CONF_UNSET_PTR + && conf->upstream.ssl_certificate_key == NGX_CONF_UNSET_PTR + && conf->upstream.ssl_passwords == NGX_CONF_UNSET_PTR + && conf->upstream.ssl_verify == NGX_CONF_UNSET + && conf->ssl_verify_depth == NGX_CONF_UNSET_UINT + && conf->ssl_trusted_certificate.data == NULL + && conf->ssl_crl.data == NULL + && conf->upstream.ssl_session_reuse == NGX_CONF_UNSET + && conf->ssl_conf_commands == NGX_CONF_UNSET_PTR) + { + if (prev->upstream.ssl) { + conf->upstream.ssl = prev->upstream.ssl; + return NGX_OK; + } - glcf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); - if (glcf->upstream.ssl == NULL) { + preserve = 1; + + } else { + preserve = 0; + } + + conf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); + if (conf->upstream.ssl == NULL) { return NGX_ERROR; } - glcf->upstream.ssl->log = cf->log; + conf->upstream.ssl->log = cf->log; + + /* + * special handling to preserve conf->upstream.ssl + * in the "http" section to inherit it to all servers + */ + + if (preserve) { + prev->upstream.ssl = conf->upstream.ssl; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf) +{ + ngx_pool_cleanup_t *cln; + + if (glcf->upstream.ssl->ctx) { + return NGX_OK; + } if (ngx_ssl_create(glcf->upstream.ssl, glcf->ssl_protocols, NULL) != NGX_OK) @@ -4902,8 +4962,9 @@ ngx_http_grpc_set_ssl(ngx_conf_t *cf, ngx_http_grpc_loc_conf_t *glcf) return NGX_ERROR; } - if (glcf->upstream.ssl_certificate) { - + if (glcf->upstream.ssl_certificate + && glcf->upstream.ssl_certificate->value.len) + { if (glcf->upstream.ssl_certificate_key == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"grpc_ssl_certificate_key\" is defined " diff --git a/src/http/modules/ngx_http_gunzip_filter_module.c b/src/http/modules/ngx_http_gunzip_filter_module.c index c1341f5..5d170a1 100644 --- a/src/http/modules/ngx_http_gunzip_filter_module.c +++ b/src/http/modules/ngx_http_gunzip_filter_module.c @@ -333,6 +333,8 @@ static ngx_int_t ngx_http_gunzip_filter_add_data(ngx_http_request_t *r, ngx_http_gunzip_ctx_t *ctx) { + ngx_chain_t *cl; + if (ctx->zstream.avail_in || ctx->flush != Z_NO_FLUSH || ctx->redo) { return NGX_OK; } @@ -344,8 +346,11 @@ ngx_http_gunzip_filter_add_data(ngx_http_request_t *r, return NGX_DECLINED; } - ctx->in_buf = ctx->in->buf; - ctx->in = ctx->in->next; + cl = ctx->in; + ctx->in_buf = cl->buf; + ctx->in = cl->next; + + ngx_free_chain(r->pool, cl); ctx->zstream.next_in = ctx->in_buf->pos; ctx->zstream.avail_in = ctx->in_buf->last - ctx->in_buf->pos; @@ -374,6 +379,7 @@ static ngx_int_t ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r, ngx_http_gunzip_ctx_t *ctx) { + ngx_chain_t *cl; ngx_http_gunzip_conf_t *conf; if (ctx->zstream.avail_out) { @@ -383,8 +389,12 @@ ngx_http_gunzip_filter_get_buf(ngx_http_request_t *r, conf = ngx_http_get_module_loc_conf(r, ngx_http_gunzip_filter_module); if (ctx->free) { - ctx->out_buf = ctx->free->buf; - ctx->free = ctx->free->next; + + cl = ctx->free; + ctx->out_buf = cl->buf; + ctx->free = cl->next; + + ngx_free_chain(r->pool, cl); ctx->out_buf->flush = 0; diff --git a/src/http/modules/ngx_http_gzip_filter_module.c b/src/http/modules/ngx_http_gzip_filter_module.c index b8c5ccc..7113df6 100644 --- a/src/http/modules/ngx_http_gzip_filter_module.c +++ b/src/http/modules/ngx_http_gzip_filter_module.c @@ -57,6 +57,7 @@ typedef struct { unsigned nomem:1; unsigned buffering:1; unsigned zlib_ng:1; + unsigned state_allocated:1; size_t zin; size_t zout; @@ -280,6 +281,7 @@ ngx_http_gzip_header_filter(ngx_http_request_t *r) } h->hash = 1; + h->next = NULL; ngx_str_set(&h->key, "Content-Encoding"); ngx_str_set(&h->value, "gzip"); r->headers_out.content_encoding = h; @@ -513,9 +515,12 @@ ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) } else { /* * Another zlib variant, https://github.com/zlib-ng/zlib-ng. - * It forces window bits to 13 for fast compression level, - * uses 16-byte padding in one of window-sized buffers, and - * uses 128K hash. + * It used to force window bits to 13 for fast compression level, + * used (64 + sizeof(void*)) additional space on all allocations + * for alignment and 16-byte padding in one of window-sized buffers, + * uses a single allocation with up to 200 bytes for alignment and + * internal pointers, 5/4 times more memory for the pending buffer, + * and 128K hash. */ if (conf->level == 1) { @@ -523,7 +528,8 @@ ngx_http_gzip_filter_memory(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) } ctx->allocated = 8192 + 16 + (1 << (wbits + 2)) - + 131072 + (1 << (memlevel + 8)); + + 131072 + (5 << (memlevel + 6)) + + 4 * (64 + sizeof(void*)); ctx->zlib_ng = 1; } } @@ -925,13 +931,16 @@ ngx_http_gzip_filter_alloc(void *opaque, u_int items, u_int size) alloc = items * size; - if (items == 1 && alloc % 512 != 0 && alloc < 8192) { - + if (items == 1 && alloc % 512 != 0 && alloc < 8192 + && !ctx->state_allocated) + { /* * The zlib deflate_state allocation, it takes about 6K, * we allocate 8K. Other allocations are divisible by 512. */ + ctx->state_allocated = 1; + alloc = 8192; } @@ -978,10 +987,14 @@ static void ngx_http_gzip_filter_free_copy_buf(ngx_http_request_t *r, ngx_http_gzip_ctx_t *ctx) { - ngx_chain_t *cl; + ngx_chain_t *cl, *ln; + + for (cl = ctx->copied; cl; /* void */) { + ln = cl; + cl = cl->next; - for (cl = ctx->copied; cl; cl = cl->next) { - ngx_pfree(r->pool, cl->buf->start); + ngx_pfree(r->pool, ln->buf->start); + ngx_free_chain(r->pool, ln); } ctx->copied = NULL; diff --git a/src/http/modules/ngx_http_gzip_static_module.c b/src/http/modules/ngx_http_gzip_static_module.c index 7652a9a..91b38d1 100644 --- a/src/http/modules/ngx_http_gzip_static_module.c +++ b/src/http/modules/ngx_http_gzip_static_module.c @@ -242,10 +242,13 @@ ngx_http_gzip_static_handler(ngx_http_request_t *r) } h->hash = 1; + h->next = NULL; ngx_str_set(&h->key, "Content-Encoding"); ngx_str_set(&h->value, "gzip"); r->headers_out.content_encoding = h; + r->allow_ranges = 1; + /* we need to allocate all before the header would be sent */ b = ngx_calloc_buf(r->pool); @@ -270,6 +273,7 @@ ngx_http_gzip_static_handler(ngx_http_request_t *r) b->in_file = b->file_last ? 1 : 0; b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; + b->sync = (b->last_buf || b->in_file) ? 0 : 1; b->file->fd = of.fd; b->file->name = path; diff --git a/src/http/modules/ngx_http_headers_filter_module.c b/src/http/modules/ngx_http_headers_filter_module.c index a4c8cc2..90e6da8 100644 --- a/src/http/modules/ngx_http_headers_filter_module.c +++ b/src/http/modules/ngx_http_headers_filter_module.c @@ -329,8 +329,7 @@ ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf) time_t now, expires_time, max_age; ngx_str_t value; ngx_int_t rc; - ngx_uint_t i; - ngx_table_elt_t *e, *cc, **ccp; + ngx_table_elt_t *e, *cc; ngx_http_expires_t expires; expires = conf->expires; @@ -363,6 +362,7 @@ ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf) } r->headers_out.expires = e; + e->next = NULL; e->hash = 1; ngx_str_set(&e->key, "Expires"); @@ -371,38 +371,29 @@ ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf) len = sizeof("Mon, 28 Sep 1970 06:00:00 GMT"); e->value.len = len - 1; - ccp = r->headers_out.cache_control.elts; + cc = r->headers_out.cache_control; - if (ccp == NULL) { - - if (ngx_array_init(&r->headers_out.cache_control, r->pool, - 1, sizeof(ngx_table_elt_t *)) - != NGX_OK) - { - return NGX_ERROR; - } + if (cc == NULL) { cc = ngx_list_push(&r->headers_out.headers); if (cc == NULL) { + e->hash = 0; return NGX_ERROR; } + r->headers_out.cache_control = cc; + cc->next = NULL; + cc->hash = 1; ngx_str_set(&cc->key, "Cache-Control"); - ccp = ngx_array_push(&r->headers_out.cache_control); - if (ccp == NULL) { - return NGX_ERROR; - } - - *ccp = cc; - } else { - for (i = 1; i < r->headers_out.cache_control.nelts; i++) { - ccp[i]->hash = 0; + for (cc = cc->next; cc; cc = cc->next) { + cc->hash = 0; } - cc = ccp[0]; + cc = r->headers_out.cache_control; + cc->next = NULL; } if (expires == NGX_HTTP_EXPIRES_EPOCH) { @@ -420,6 +411,8 @@ ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf) e->value.data = ngx_pnalloc(r->pool, len); if (e->value.data == NULL) { + e->hash = 0; + cc->hash = 0; return NGX_ERROR; } @@ -457,6 +450,7 @@ ngx_http_set_expires(ngx_http_request_t *r, ngx_http_headers_conf_t *conf) cc->value.data = ngx_pnalloc(r->pool, sizeof("max-age=") + NGX_TIME_T_LEN + 1); if (cc->value.data == NULL) { + cc->hash = 0; return NGX_ERROR; } @@ -564,22 +558,12 @@ static ngx_int_t ngx_http_add_multi_header_lines(ngx_http_request_t *r, ngx_http_header_val_t *hv, ngx_str_t *value) { - ngx_array_t *pa; ngx_table_elt_t *h, **ph; if (value->len == 0) { return NGX_OK; } - pa = (ngx_array_t *) ((char *) &r->headers_out + hv->offset); - - if (pa->elts == NULL) { - if (ngx_array_init(pa, r->pool, 1, sizeof(ngx_table_elt_t *)) != NGX_OK) - { - return NGX_ERROR; - } - } - h = ngx_list_push(&r->headers_out.headers); if (h == NULL) { return NGX_ERROR; @@ -589,12 +573,12 @@ ngx_http_add_multi_header_lines(ngx_http_request_t *r, h->key = hv->key; h->value = *value; - ph = ngx_array_push(pa); - if (ph == NULL) { - return NGX_ERROR; - } + ph = (ngx_table_elt_t **) ((char *) &r->headers_out + hv->offset); + + while (*ph) { ph = &(*ph)->next; } *ph = h; + h->next = NULL; return NGX_OK; } @@ -642,6 +626,7 @@ ngx_http_set_response_header(ngx_http_request_t *r, ngx_http_header_val_t *hv, } *old = h; + h->next = NULL; } h->hash = 1; diff --git a/src/http/modules/ngx_http_memcached_module.c b/src/http/modules/ngx_http_memcached_module.c index c82df6e..11bbd91 100644 --- a/src/http/modules/ngx_http_memcached_module.c +++ b/src/http/modules/ngx_http_memcached_module.c @@ -401,6 +401,7 @@ found: } h->hash = 1; + h->next = NULL; ngx_str_set(&h->key, "Content-Encoding"); ngx_str_set(&h->value, "gzip"); r->headers_out.content_encoding = h; diff --git a/src/http/modules/ngx_http_mp4_module.c b/src/http/modules/ngx_http_mp4_module.c index 4eff01e..b7bd192 100644 --- a/src/http/modules/ngx_http_mp4_module.c +++ b/src/http/modules/ngx_http_mp4_module.c @@ -714,6 +714,7 @@ ngx_http_mp4_handler(ngx_http_request_t *r) b->in_file = b->file_last ? 1 : 0; b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; + b->sync = (b->last_buf || b->in_file) ? 0 : 1; b->file->fd = of.fd; b->file->name = path; @@ -2430,7 +2431,7 @@ ngx_http_mp4_crop_stts_data(ngx_http_mp4_file_t *mp4, } start_sample += count; - start_time -= count * duration; + start_time -= (uint64_t) count * duration; entries--; entry++; } @@ -3098,7 +3099,8 @@ static ngx_int_t ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4, ngx_http_mp4_trak_t *trak, ngx_uint_t start) { - uint32_t start_sample, chunk, samples, id, next_chunk, n, + uint64_t n; + uint32_t start_sample, chunk, samples, id, next_chunk, prev_samples; ngx_buf_t *data, *buf; ngx_uint_t entries, target_chunk, chunk_samples; @@ -3154,12 +3156,19 @@ ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4, next_chunk = ngx_mp4_get_32value(entry->chunk); + if (next_chunk < chunk) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "unordered mp4 stsc chunks in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "sample:%uD, chunk:%uD, chunks:%uD, " "samples:%uD, id:%uD", start_sample, chunk, next_chunk - chunk, samples, id); - n = (next_chunk - chunk) * samples; + n = (uint64_t) (next_chunk - chunk) * samples; if (start_sample < n) { goto found; @@ -3167,7 +3176,10 @@ ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4, start_sample -= n; - prev_samples = samples; + if (next_chunk > chunk) { + prev_samples = samples; + } + chunk = next_chunk; samples = ngx_mp4_get_32value(entry->samples); id = ngx_mp4_get_32value(entry->id); @@ -3177,11 +3189,18 @@ ngx_http_mp4_crop_stsc_data(ngx_http_mp4_file_t *mp4, next_chunk = trak->chunks + 1; + if (next_chunk < chunk) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "unordered mp4 stsc chunks in \"%s\"", + mp4->file.name.data); + return NGX_ERROR; + } + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, mp4->file.log, 0, "sample:%uD, chunk:%uD, chunks:%uD, samples:%uD", start_sample, chunk, next_chunk - chunk, samples); - n = (next_chunk - chunk) * samples; + n = (uint64_t) (next_chunk - chunk) * samples; if (start_sample > n) { ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, @@ -3202,6 +3221,12 @@ found: return NGX_ERROR; } + if (chunk == 0) { + ngx_log_error(NGX_LOG_ERR, mp4->file.log, 0, + "zero chunk in \"%s\"", mp4->file.name.data); + return NGX_ERROR; + } + target_chunk = chunk - 1; target_chunk += start_sample / samples; chunk_samples = start_sample % samples; diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c index 7c4061c..9cc202c 100644 --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -236,6 +236,8 @@ static ngx_int_t ngx_http_proxy_rewrite_regex(ngx_conf_t *cf, ngx_http_proxy_rewrite_t *pr, ngx_str_t *regex, ngx_uint_t caseless); #if (NGX_HTTP_SSL) +static ngx_int_t ngx_http_proxy_merge_ssl(ngx_conf_t *cf, + ngx_http_proxy_loc_conf_t *conf, ngx_http_proxy_loc_conf_t *prev); static ngx_int_t ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf); #endif @@ -959,7 +961,7 @@ ngx_http_proxy_handler(ngx_http_request_t *r) ctx->vars = plcf->vars; u->schema = plcf->vars.schema; #if (NGX_HTTP_SSL) - u->ssl = (plcf->upstream.ssl != NULL); + u->ssl = plcf->ssl; #endif } else { @@ -1930,8 +1932,12 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return NGX_ERROR; + if (hh) { + rc = hh->handler(r, h, hh->offset); + + if (rc != NGX_OK) { + return rc; + } } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -1965,6 +1971,7 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) ngx_str_set(&h->key, "Server"); ngx_str_null(&h->value); h->lowcase_key = (u_char *) "server"; + h->next = NULL; } if (r->upstream->headers_in.date == NULL) { @@ -1978,6 +1985,7 @@ ngx_http_proxy_process_header(ngx_http_request_t *r) ngx_str_set(&h->key, "Date"); ngx_str_null(&h->value); h->lowcase_key = (u_char *) "date"; + h->next = NULL; } /* clear content length if response is chunked */ @@ -2559,22 +2567,20 @@ static ngx_int_t ngx_http_proxy_add_x_forwarded_for_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - size_t len; - u_char *p; - ngx_uint_t i, n; - ngx_table_elt_t **h; + size_t len; + u_char *p; + ngx_table_elt_t *h, *xfwd; v->valid = 1; v->no_cacheable = 0; v->not_found = 0; - n = r->headers_in.x_forwarded_for.nelts; - h = r->headers_in.x_forwarded_for.elts; + xfwd = r->headers_in.x_forwarded_for; len = 0; - for (i = 0; i < n; i++) { - len += h[i]->value.len + sizeof(", ") - 1; + for (h = xfwd; h; h = h->next) { + len += h->value.len + sizeof(", ") - 1; } if (len == 0) { @@ -2593,8 +2599,8 @@ ngx_http_proxy_add_x_forwarded_for_variable(ngx_http_request_t *r, v->len = len; v->data = p; - for (i = 0; i < n; i++) { - p = ngx_copy(p, h[i]->value.data, h[i]->value.len); + for (h = xfwd; h; h = h->next) { + p = ngx_copy(p, h->value.data, h->value.len); *p++ = ','; *p++ = ' '; } @@ -3720,12 +3726,17 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) #if (NGX_HTTP_SSL) + if (ngx_http_proxy_merge_ssl(cf, conf, prev) != NGX_OK) { + return NGX_CONF_ERROR; + } + ngx_conf_merge_value(conf->upstream.ssl_session_reuse, prev->upstream.ssl_session_reuse, 1); ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols, - (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 - |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); + (NGX_CONF_BITMASK_SET + |NGX_SSL_TLSv1|NGX_SSL_TLSv1_1 + |NGX_SSL_TLSv1_2|NGX_SSL_TLSv1_3)); ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT"); @@ -3853,7 +3864,7 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) conf->proxy_values = prev->proxy_values; #if (NGX_HTTP_SSL) - conf->upstream.ssl = prev->upstream.ssl; + conf->ssl = prev->ssl; #endif } @@ -4919,16 +4930,62 @@ ngx_http_proxy_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) static ngx_int_t -ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf) +ngx_http_proxy_merge_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf, + ngx_http_proxy_loc_conf_t *prev) { - ngx_pool_cleanup_t *cln; + ngx_uint_t preserve; + + if (conf->ssl_protocols == 0 + && conf->ssl_ciphers.data == NULL + && conf->upstream.ssl_certificate == NGX_CONF_UNSET_PTR + && conf->upstream.ssl_certificate_key == NGX_CONF_UNSET_PTR + && conf->upstream.ssl_passwords == NGX_CONF_UNSET_PTR + && conf->upstream.ssl_verify == NGX_CONF_UNSET + && conf->ssl_verify_depth == NGX_CONF_UNSET_UINT + && conf->ssl_trusted_certificate.data == NULL + && conf->ssl_crl.data == NULL + && conf->upstream.ssl_session_reuse == NGX_CONF_UNSET + && conf->ssl_conf_commands == NGX_CONF_UNSET_PTR) + { + if (prev->upstream.ssl) { + conf->upstream.ssl = prev->upstream.ssl; + return NGX_OK; + } + + preserve = 1; + + } else { + preserve = 0; + } - plcf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); - if (plcf->upstream.ssl == NULL) { + conf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); + if (conf->upstream.ssl == NULL) { return NGX_ERROR; } - plcf->upstream.ssl->log = cf->log; + conf->upstream.ssl->log = cf->log; + + /* + * special handling to preserve conf->upstream.ssl + * in the "http" section to inherit it to all servers + */ + + if (preserve) { + prev->upstream.ssl = conf->upstream.ssl; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf) +{ + ngx_pool_cleanup_t *cln; + + if (plcf->upstream.ssl->ctx) { + return NGX_OK; + } if (ngx_ssl_create(plcf->upstream.ssl, plcf->ssl_protocols, NULL) != NGX_OK) @@ -4951,8 +5008,9 @@ ngx_http_proxy_set_ssl(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *plcf) return NGX_ERROR; } - if (plcf->upstream.ssl_certificate) { - + if (plcf->upstream.ssl_certificate + && plcf->upstream.ssl_certificate->value.len) + { if (plcf->upstream.ssl_certificate_key == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"proxy_ssl_certificate_key\" is defined " diff --git a/src/http/modules/ngx_http_range_filter_module.c b/src/http/modules/ngx_http_range_filter_module.c index ae08ebb..27d1875 100644 --- a/src/http/modules/ngx_http_range_filter_module.c +++ b/src/http/modules/ngx_http_range_filter_module.c @@ -258,6 +258,7 @@ next_filter: } r->headers_out.accept_ranges->hash = 1; + r->headers_out.accept_ranges->next = NULL; ngx_str_set(&r->headers_out.accept_ranges->key, "Accept-Ranges"); ngx_str_set(&r->headers_out.accept_ranges->value, "bytes"); @@ -424,9 +425,14 @@ ngx_http_range_singlepart_header(ngx_http_request_t *r, return NGX_ERROR; } + if (r->headers_out.content_range) { + r->headers_out.content_range->hash = 0; + } + r->headers_out.content_range = content_range; content_range->hash = 1; + content_range->next = NULL; ngx_str_set(&content_range->key, "Content-Range"); content_range->value.data = ngx_pnalloc(r->pool, @@ -580,6 +586,11 @@ ngx_http_range_multipart_header(ngx_http_request_t *r, r->headers_out.content_length = NULL; } + if (r->headers_out.content_range) { + r->headers_out.content_range->hash = 0; + r->headers_out.content_range = NULL; + } + return ngx_http_next_header_filter(r); } @@ -596,9 +607,14 @@ ngx_http_range_not_satisfiable(ngx_http_request_t *r) return NGX_ERROR; } + if (r->headers_out.content_range) { + r->headers_out.content_range->hash = 0; + } + r->headers_out.content_range = content_range; content_range->hash = 1; + content_range->next = NULL; ngx_str_set(&content_range->key, "Content-Range"); content_range->value.data = ngx_pnalloc(r->pool, diff --git a/src/http/modules/ngx_http_realip_module.c b/src/http/modules/ngx_http_realip_module.c index 9586ebe..f6731e7 100644 --- a/src/http/modules/ngx_http_realip_module.c +++ b/src/http/modules/ngx_http_realip_module.c @@ -134,9 +134,8 @@ ngx_http_realip_handler(ngx_http_request_t *r) ngx_str_t *value; ngx_uint_t i, hash; ngx_addr_t addr; - ngx_array_t *xfwd; ngx_list_part_t *part; - ngx_table_elt_t *header; + ngx_table_elt_t *header, *xfwd; ngx_connection_t *c; ngx_http_realip_ctx_t *ctx; ngx_http_realip_loc_conf_t *rlcf; @@ -168,9 +167,9 @@ ngx_http_realip_handler(ngx_http_request_t *r) case NGX_HTTP_REALIP_XFWD: - xfwd = &r->headers_in.x_forwarded_for; + xfwd = r->headers_in.x_forwarded_for; - if (xfwd->elts == NULL) { + if (xfwd == NULL) { return NGX_DECLINED; } diff --git a/src/http/modules/ngx_http_referer_module.c b/src/http/modules/ngx_http_referer_module.c index 599dd3a..11a681b 100644 --- a/src/http/modules/ngx_http_referer_module.c +++ b/src/http/modules/ngx_http_referer_module.c @@ -631,7 +631,7 @@ ngx_http_add_regex_referer(ngx_conf_t *cf, ngx_http_referer_conf_t *rlcf, #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "the using of the regex \"%V\" requires PCRE library", + "using regex \"%V\" requires PCRE library", name); return NGX_ERROR; diff --git a/src/http/modules/ngx_http_rewrite_module.c b/src/http/modules/ngx_http_rewrite_module.c index 0e6d4df..ff3b687 100644 --- a/src/http/modules/ngx_http_rewrite_module.c +++ b/src/http/modules/ngx_http_rewrite_module.c @@ -489,6 +489,7 @@ ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } if (cf->args->nelts == 2) { + ngx_str_set(&ret->text.value, ""); return NGX_CONF_OK; } diff --git a/src/http/modules/ngx_http_scgi_module.c b/src/http/modules/ngx_http_scgi_module.c index e5d31ae..3acea87 100644 --- a/src/http/modules/ngx_http_scgi_module.c +++ b/src/http/modules/ngx_http_scgi_module.c @@ -633,14 +633,14 @@ static ngx_int_t ngx_http_scgi_create_request(ngx_http_request_t *r) { off_t content_length_n; - u_char ch, *key, *val, *lowcase_key; + u_char ch, sep, *key, *val, *lowcase_key; size_t len, key_len, val_len, allocated; ngx_buf_t *b; ngx_str_t content_length; ngx_uint_t i, n, hash, skip_empty, header_params; ngx_chain_t *cl, *body; ngx_list_part_t *part; - ngx_table_elt_t *header, **ignored; + ngx_table_elt_t *header, *hn, **ignored; ngx_http_scgi_params_t *params; ngx_http_script_code_pt code; ngx_http_script_engine_t e, le; @@ -707,7 +707,11 @@ ngx_http_scgi_create_request(ngx_http_request_t *r) allocated = 0; lowcase_key = NULL; - if (params->number) { + if (ngx_http_link_multi_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + if (params->number || r->headers_in.multi) { n = 0; part = &r->headers_in.headers.part; @@ -737,6 +741,12 @@ ngx_http_scgi_create_request(ngx_http_request_t *r) i = 0; } + for (n = 0; n < header_params; n++) { + if (&header[i] == ignored[n]) { + goto next_length; + } + } + if (params->number) { if (allocated < header[i].key.len) { allocated = header[i].key.len + 16; @@ -770,6 +780,15 @@ ngx_http_scgi_create_request(ngx_http_request_t *r) len += sizeof("HTTP_") - 1 + header[i].key.len + 1 + header[i].value.len + 1; + + for (hn = header[i].next; hn; hn = hn->next) { + len += hn->value.len + 2; + ignored[header_params++] = hn; + } + + next_length: + + continue; } } @@ -869,7 +888,7 @@ ngx_http_scgi_create_request(ngx_http_request_t *r) for (n = 0; n < header_params; n++) { if (&header[i] == ignored[n]) { - goto next; + goto next_value; } } @@ -893,12 +912,33 @@ ngx_http_scgi_create_request(ngx_http_request_t *r) val = b->last; b->last = ngx_copy(val, header[i].value.data, header[i].value.len); + + if (header[i].next) { + + if (header[i].key.len == sizeof("Cookie") - 1 + && ngx_strncasecmp(header[i].key.data, (u_char *) "Cookie", + sizeof("Cookie") - 1) + == 0) + { + sep = ';'; + + } else { + sep = ','; + } + + for (hn = header[i].next; hn; hn = hn->next) { + *b->last++ = sep; + *b->last++ = ' '; + b->last = ngx_copy(b->last, hn->value.data, hn->value.len); + } + } + *b->last++ = (u_char) 0; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "scgi param: \"%s: %s\"", key, val); - next: + next_value: continue; } @@ -1074,8 +1114,12 @@ ngx_http_scgi_process_header(ngx_http_request_t *r) hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return NGX_ERROR; + if (hh) { + rc = hh->handler(r, h, hh->offset); + + if (rc != NGX_OK) { + return rc; + } } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -1109,7 +1153,10 @@ ngx_http_scgi_process_header(ngx_http_request_t *r) } u->headers_in.status_n = status; - u->headers_in.status_line = *status_line; + + if (status_line->len > 3) { + u->headers_in.status_line = *status_line; + } } else if (u->headers_in.location) { u->headers_in.status_n = 302; diff --git a/src/http/modules/ngx_http_ssi_filter_module.c b/src/http/modules/ngx_http_ssi_filter_module.c index 6737965..47068f7 100644 --- a/src/http/modules/ngx_http_ssi_filter_module.c +++ b/src/http/modules/ngx_http_ssi_filter_module.c @@ -329,7 +329,7 @@ static ngx_http_variable_t ngx_http_ssi_vars[] = { static ngx_int_t ngx_http_ssi_header_filter(ngx_http_request_t *r) { - ngx_http_ssi_ctx_t *ctx; + ngx_http_ssi_ctx_t *ctx, *mctx; ngx_http_ssi_loc_conf_t *slcf; slcf = ngx_http_get_module_loc_conf(r, ngx_http_ssi_filter_module); @@ -341,6 +341,8 @@ ngx_http_ssi_header_filter(ngx_http_request_t *r) return ngx_http_next_header_filter(r); } + mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module); + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_ssi_ctx_t)); if (ctx == NULL) { return NGX_ERROR; @@ -367,6 +369,26 @@ ngx_http_ssi_header_filter(ngx_http_request_t *r) r->filter_need_in_memory = 1; if (r == r->main) { + + if (mctx) { + + /* + * if there was a shared context previously used as main, + * copy variables and blocks + */ + + ctx->variables = mctx->variables; + ctx->blocks = mctx->blocks; + +#if (NGX_PCRE) + ctx->ncaptures = mctx->ncaptures; + ctx->captures = mctx->captures; + ctx->captures_data = mctx->captures_data; +#endif + + mctx->shared = 0; + } + ngx_http_clear_content_length(r); ngx_http_clear_accept_ranges(r); @@ -379,6 +401,10 @@ ngx_http_ssi_header_filter(ngx_http_request_t *r) } else { ngx_http_weak_etag(r); } + + } else if (mctx == NULL) { + ngx_http_set_ctx(r->main, ctx, ngx_http_ssi_filter_module); + ctx->shared = 1; } return ngx_http_next_header_filter(r); @@ -405,6 +431,7 @@ ngx_http_ssi_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ctx = ngx_http_get_module_ctx(r, ngx_http_ssi_filter_module); if (ctx == NULL + || (ctx->shared && r == r->main) || (in == NULL && ctx->buf == NULL && ctx->in == NULL @@ -455,9 +482,13 @@ ngx_http_ssi_body_filter(ngx_http_request_t *r, ngx_chain_t *in) while (ctx->in || ctx->buf) { if (ctx->buf == NULL) { - ctx->buf = ctx->in->buf; - ctx->in = ctx->in->next; + + cl = ctx->in; + ctx->buf = cl->buf; + ctx->in = cl->next; ctx->pos = ctx->buf->pos; + + ngx_free_chain(r->pool, cl); } if (ctx->state == ssi_start_state) { @@ -1974,7 +2005,7 @@ ngx_http_ssi_regex_match(ngx_http_request_t *r, ngx_str_t *pattern, #else ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, - "the using of the regex \"%V\" in SSI requires PCRE library", + "using regex \"%V\" in SSI requires PCRE library", pattern); return NGX_HTTP_SSI_ERROR; diff --git a/src/http/modules/ngx_http_ssi_filter_module.h b/src/http/modules/ngx_http_ssi_filter_module.h index 0bd01a0..419bf97 100644 --- a/src/http/modules/ngx_http_ssi_filter_module.h +++ b/src/http/modules/ngx_http_ssi_filter_module.h @@ -71,6 +71,7 @@ typedef struct { u_char *captures_data; #endif + unsigned shared:1; unsigned conditional:2; unsigned encoding:2; unsigned block:1; diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index d74d460..1c92d9f 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -9,6 +9,10 @@ #include <ngx_core.h> #include <ngx_http.h> +#if (NGX_QUIC_OPENSSL_COMPAT) +#include <ngx_event_quic_openssl_compat.h> +#endif + typedef ngx_int_t (*ngx_ssl_variable_handler_pt)(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); @@ -39,8 +43,6 @@ static char *ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, static ngx_int_t ngx_http_ssl_compile_certificates(ngx_conf_t *cf, ngx_http_ssl_srv_conf_t *conf); -static char *ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); static char *ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, @@ -52,6 +54,10 @@ static char *ngx_http_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data); static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf); +#if (NGX_QUIC_OPENSSL_COMPAT) +static ngx_int_t ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, + ngx_http_conf_addr_t *addr); +#endif static ngx_conf_bitmask_t ngx_http_ssl_protocols[] = { @@ -82,24 +88,12 @@ static ngx_conf_enum_t ngx_http_ssl_ocsp[] = { }; -static ngx_conf_deprecated_t ngx_http_ssl_deprecated = { - ngx_conf_deprecated, "ssl", "listen ... ssl" -}; - - static ngx_conf_post_t ngx_http_ssl_conf_command_post = { ngx_http_ssl_conf_command_check }; static ngx_command_t ngx_http_ssl_commands[] = { - { ngx_string("ssl"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, - ngx_http_ssl_enable, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_ssl_srv_conf_t, enable), - &ngx_http_ssl_deprecated }, - { ngx_string("ssl_certificate"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, @@ -419,16 +413,22 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, unsigned char *outlen, const unsigned char *in, unsigned int inlen, void *arg) { - unsigned int srvlen; - unsigned char *srv; + unsigned int srvlen; + unsigned char *srv; #if (NGX_DEBUG) - unsigned int i; + unsigned int i; +#endif +#if (NGX_HTTP_V2 || NGX_HTTP_V3) + ngx_http_connection_t *hc; #endif #if (NGX_HTTP_V2) - ngx_http_connection_t *hc; + ngx_http_v2_srv_conf_t *h2scf; #endif -#if (NGX_HTTP_V2 || NGX_DEBUG) - ngx_connection_t *c; +#if (NGX_HTTP_V3) + ngx_http_v3_srv_conf_t *h3scf; +#endif +#if (NGX_HTTP_V2 || NGX_HTTP_V3 || NGX_DEBUG) + ngx_connection_t *c; c = ngx_ssl_get_connection(ssl_conn); #endif @@ -441,17 +441,49 @@ ngx_http_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, const unsigned char **out, } #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) hc = c->data; +#endif + +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + if (h3scf->enable && h3scf->enable_hq) { + srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO + NGX_HTTP_V3_HQ_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO NGX_HTTP_V3_HQ_ALPN_PROTO) + - 1; + + } else if (h3scf->enable_hq) { + srv = (unsigned char *) NGX_HTTP_V3_HQ_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1; + + } else if (h3scf->enable) { + srv = (unsigned char *) NGX_HTTP_V3_ALPN_PROTO; + srvlen = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; + + } else { + return SSL_TLSEXT_ERR_ALERT_FATAL; + } - if (hc->addr_conf->http2) { - srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS; - srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1; } else #endif { - srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS; - srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1; +#if (NGX_HTTP_V2) + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + if (h2scf->enable || hc->addr_conf->http2) { + srv = (unsigned char *) NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS; + srvlen = sizeof(NGX_HTTP_V2_ALPN_PROTO NGX_HTTP_ALPN_PROTOS) - 1; + + } else +#endif + { + srv = (unsigned char *) NGX_HTTP_ALPN_PROTOS; + srvlen = sizeof(NGX_HTTP_ALPN_PROTOS) - 1; + } } if (SSL_select_next_proto((unsigned char **) out, outlen, srv, srvlen, @@ -579,7 +611,6 @@ ngx_http_ssl_create_srv_conf(ngx_conf_t *cf) * sscf->stapling_responder = { 0, NULL }; */ - sscf->enable = NGX_CONF_UNSET; sscf->prefer_server_ciphers = NGX_CONF_UNSET; sscf->early_data = NGX_CONF_UNSET; sscf->reject_handshake = NGX_CONF_UNSET; @@ -611,17 +642,6 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_pool_cleanup_t *cln; - if (conf->enable == NGX_CONF_UNSET) { - if (prev->enable == NGX_CONF_UNSET) { - conf->enable = 0; - - } else { - conf->enable = prev->enable; - conf->file = prev->file; - conf->line = prev->line; - } - } - ngx_conf_merge_value(conf->session_timeout, prev->session_timeout, 300); @@ -632,8 +652,9 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->reject_handshake, prev->reject_handshake, 0); ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols, - (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 - |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); + (NGX_CONF_BITMASK_SET + |NGX_SSL_TLSv1|NGX_SSL_TLSv1_1 + |NGX_SSL_TLSv1_2|NGX_SSL_TLSv1_3)); ngx_conf_merge_size_value(conf->buffer_size, prev->buffer_size, NGX_SSL_BUFSIZE); @@ -675,37 +696,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) conf->ssl.log = cf->log; - if (conf->enable) { - - if (conf->certificates) { - if (conf->certificate_keys == NULL) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate_key\" is defined for " - "the \"ssl\" directive in %s:%ui", - conf->file, conf->line); - return NGX_CONF_ERROR; - } - - if (conf->certificate_keys->nelts < conf->certificates->nelts) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate_key\" is defined " - "for certificate \"%V\" and " - "the \"ssl\" directive in %s:%ui", - ((ngx_str_t *) conf->certificates->elts) - + conf->certificates->nelts - 1, - conf->file, conf->line); - return NGX_CONF_ERROR; - } - - } else if (!conf->reject_handshake) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate\" is defined for " - "the \"ssl\" directive in %s:%ui", - conf->file, conf->line); - return NGX_CONF_ERROR; - } - - } else if (conf->certificates) { + if (conf->certificates) { if (conf->certificate_keys == NULL || conf->certificate_keys->nelts < conf->certificates->nelts) @@ -991,26 +982,6 @@ found: } -static char * -ngx_http_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_http_ssl_srv_conf_t *sscf = conf; - - char *rv; - - rv = ngx_conf_set_flag_slot(cf, cmd, conf); - - if (rv != NGX_CONF_OK) { - return rv; - } - - sscf->file = cf->conf_file->file.name.data; - sscf->line = cf->conf_file->line; - - return NGX_CONF_OK; -} - - static char * ngx_http_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { @@ -1093,7 +1064,7 @@ ngx_http_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) len++; } - if (len == 0) { + if (len == 0 || j == value[i].len) { goto invalid; } @@ -1183,7 +1154,7 @@ ngx_http_ssl_ocsp_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) len++; } - if (len == 0) { + if (len == 0 || j == value[1].len) { goto invalid; } @@ -1240,6 +1211,7 @@ static ngx_int_t ngx_http_ssl_init(ngx_conf_t *cf) { ngx_uint_t a, p, s; + const char *name; ngx_http_conf_addr_t *addr; ngx_http_conf_port_t *port; ngx_http_ssl_srv_conf_t *sscf; @@ -1289,22 +1261,44 @@ ngx_http_ssl_init(ngx_conf_t *cf) addr = port[p].addrs.elts; for (a = 0; a < port[p].addrs.nelts; a++) { - if (!addr[a].opt.ssl) { + if (!addr[a].opt.ssl && !addr[a].opt.quic) { continue; } + if (addr[a].opt.quic) { + name = "quic"; + +#if (NGX_QUIC_OPENSSL_COMPAT) + if (ngx_http_ssl_quic_compat_init(cf, &addr[a]) != NGX_OK) { + return NGX_ERROR; + } +#endif + + } else { + name = "ssl"; + } + cscf = addr[a].default_server; sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; if (sscf->certificates) { + + if (addr[a].opt.quic && !(sscf->protocols & NGX_SSL_TLSv1_3)) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "\"ssl_protocols\" must enable TLSv1.3 for " + "the \"listen ... %s\" directive in %s:%ui", + name, cscf->file_name, cscf->line); + return NGX_ERROR; + } + continue; } if (!sscf->reject_handshake) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"ssl_certificate\" is defined for " - "the \"listen ... ssl\" directive in %s:%ui", - cscf->file_name, cscf->line); + "the \"listen ... %s\" directive in %s:%ui", + name, cscf->file_name, cscf->line); return NGX_ERROR; } @@ -1325,8 +1319,8 @@ ngx_http_ssl_init(ngx_conf_t *cf) ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"ssl_certificate\" is defined for " - "the \"listen ... ssl\" directive in %s:%ui", - cscf->file_name, cscf->line); + "the \"listen ... %s\" directive in %s:%ui", + name, cscf->file_name, cscf->line); return NGX_ERROR; } } @@ -1334,3 +1328,31 @@ ngx_http_ssl_init(ngx_conf_t *cf) return NGX_OK; } + + +#if (NGX_QUIC_OPENSSL_COMPAT) + +static ngx_int_t +ngx_http_ssl_quic_compat_init(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) +{ + ngx_uint_t s; + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_core_srv_conf_t **cscfp, *cscf; + + cscfp = addr->servers.elts; + for (s = 0; s < addr->servers.nelts; s++) { + + cscf = cscfp[s]; + sscf = cscf->ctx->srv_conf[ngx_http_ssl_module.ctx_index]; + + if (sscf->certificates || sscf->reject_handshake) { + if (ngx_quic_compat_init(cf, sscf->ssl.ctx) != NGX_OK) { + return NGX_ERROR; + } + } + } + + return NGX_OK; +} + +#endif diff --git a/src/http/modules/ngx_http_ssl_module.h b/src/http/modules/ngx_http_ssl_module.h index 7ab0f7e..c69c8ff 100644 --- a/src/http/modules/ngx_http_ssl_module.h +++ b/src/http/modules/ngx_http_ssl_module.h @@ -15,8 +15,6 @@ typedef struct { - ngx_flag_t enable; - ngx_ssl_t ssl; ngx_flag_t prefer_server_ciphers; @@ -64,9 +62,6 @@ typedef struct { ngx_flag_t stapling_verify; ngx_str_t stapling_file; ngx_str_t stapling_responder; - - u_char *file; - ngx_uint_t line; } ngx_http_ssl_srv_conf_t; diff --git a/src/http/modules/ngx_http_static_module.c b/src/http/modules/ngx_http_static_module.c index cf29d5a..8b0bb14 100644 --- a/src/http/modules/ngx_http_static_module.c +++ b/src/http/modules/ngx_http_static_module.c @@ -195,6 +195,7 @@ ngx_http_static_handler(ngx_http_request_t *r) } r->headers_out.location->hash = 1; + r->headers_out.location->next = NULL; ngx_str_set(&r->headers_out.location->key, "Location"); r->headers_out.location->value.len = len; r->headers_out.location->value.data = location; @@ -237,10 +238,6 @@ ngx_http_static_handler(ngx_http_request_t *r) return NGX_HTTP_INTERNAL_SERVER_ERROR; } - if (r != r->main && of.size == 0) { - return ngx_http_send_header(r); - } - r->allow_ranges = 1; /* we need to allocate all before the header would be sent */ @@ -264,9 +261,10 @@ ngx_http_static_handler(ngx_http_request_t *r) b->file_pos = 0; b->file_last = of.size; - b->in_file = b->file_last ? 1: 0; - b->last_buf = (r == r->main) ? 1: 0; + b->in_file = b->file_last ? 1 : 0; + b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; + b->sync = (b->last_buf || b->in_file) ? 0 : 1; b->file->fd = of.fd; b->file->name = path; diff --git a/src/http/modules/ngx_http_sub_filter_module.c b/src/http/modules/ngx_http_sub_filter_module.c index 6d3de59..456bb27 100644 --- a/src/http/modules/ngx_http_sub_filter_module.c +++ b/src/http/modules/ngx_http_sub_filter_module.c @@ -335,9 +335,13 @@ ngx_http_sub_body_filter(ngx_http_request_t *r, ngx_chain_t *in) while (ctx->in || ctx->buf) { if (ctx->buf == NULL) { - ctx->buf = ctx->in->buf; - ctx->in = ctx->in->next; + + cl = ctx->in; + ctx->buf = cl->buf; + ctx->in = cl->next; ctx->pos = ctx->buf->pos; + + ngx_free_chain(r->pool, cl); } if (ctx->buf->flush || ctx->buf->recycled) { diff --git a/src/http/modules/ngx_http_userid_filter_module.c b/src/http/modules/ngx_http_userid_filter_module.c index 1e33c5c..e528444 100644 --- a/src/http/modules/ngx_http_userid_filter_module.c +++ b/src/http/modules/ngx_http_userid_filter_module.c @@ -319,10 +319,9 @@ ngx_http_userid_set_variable(ngx_http_request_t *r, static ngx_http_userid_ctx_t * ngx_http_userid_get_uid(ngx_http_request_t *r, ngx_http_userid_conf_t *conf) { - ngx_int_t n; - ngx_str_t src, dst; - ngx_table_elt_t **cookies; - ngx_http_userid_ctx_t *ctx; + ngx_str_t src, dst; + ngx_table_elt_t *cookie; + ngx_http_userid_ctx_t *ctx; ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module); @@ -339,9 +338,9 @@ ngx_http_userid_get_uid(ngx_http_request_t *r, ngx_http_userid_conf_t *conf) ngx_http_set_ctx(r, ctx, ngx_http_userid_filter_module); } - n = ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &conf->name, - &ctx->cookie); - if (n == NGX_DECLINED) { + cookie = ngx_http_parse_multi_header_lines(r, r->headers_in.cookie, + &conf->name, &ctx->cookie); + if (cookie == NULL) { return ctx; } @@ -349,10 +348,9 @@ ngx_http_userid_get_uid(ngx_http_request_t *r, ngx_http_userid_conf_t *conf) "uid cookie: \"%V\"", &ctx->cookie); if (ctx->cookie.len < 22) { - cookies = r->headers_in.cookies.elts; ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "client sent too short userid cookie \"%V\"", - &cookies[n]->value); + &cookie->value); return ctx; } @@ -370,10 +368,9 @@ ngx_http_userid_get_uid(ngx_http_request_t *r, ngx_http_userid_conf_t *conf) dst.data = (u_char *) ctx->uid_got; if (ngx_decode_base64(&dst, &src) == NGX_ERROR) { - cookies = r->headers_in.cookies.elts; ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "client sent invalid userid cookie \"%V\"", - &cookies[n]->value); + &cookie->value); return ctx; } diff --git a/src/http/modules/ngx_http_uwsgi_module.c b/src/http/modules/ngx_http_uwsgi_module.c index d46741a..c1731ff 100644 --- a/src/http/modules/ngx_http_uwsgi_module.c +++ b/src/http/modules/ngx_http_uwsgi_module.c @@ -96,6 +96,8 @@ static char *ngx_http_uwsgi_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_http_uwsgi_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data); +static ngx_int_t ngx_http_uwsgi_merge_ssl(ngx_conf_t *cf, + ngx_http_uwsgi_loc_conf_t *conf, ngx_http_uwsgi_loc_conf_t *prev); static ngx_int_t ngx_http_uwsgi_set_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *uwcf); #endif @@ -668,7 +670,7 @@ ngx_http_uwsgi_handler(ngx_http_request_t *r) if (uwcf->uwsgi_lengths == NULL) { #if (NGX_HTTP_SSL) - u->ssl = (uwcf->upstream.ssl != NULL); + u->ssl = uwcf->ssl; if (u->ssl) { ngx_str_set(&u->schema, "suwsgi://"); @@ -845,13 +847,13 @@ ngx_http_uwsgi_create_key(ngx_http_request_t *r) static ngx_int_t ngx_http_uwsgi_create_request(ngx_http_request_t *r) { - u_char ch, *lowcase_key; + u_char ch, sep, *lowcase_key; size_t key_len, val_len, len, allocated; ngx_uint_t i, n, hash, skip_empty, header_params; ngx_buf_t *b; ngx_chain_t *cl, *body; ngx_list_part_t *part; - ngx_table_elt_t *header, **ignored; + ngx_table_elt_t *header, *hn, **ignored; ngx_http_uwsgi_params_t *params; ngx_http_script_code_pt code; ngx_http_script_engine_t e, le; @@ -905,7 +907,11 @@ ngx_http_uwsgi_create_request(ngx_http_request_t *r) allocated = 0; lowcase_key = NULL; - if (params->number) { + if (ngx_http_link_multi_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + if (params->number || r->headers_in.multi) { n = 0; part = &r->headers_in.headers.part; @@ -935,6 +941,12 @@ ngx_http_uwsgi_create_request(ngx_http_request_t *r) i = 0; } + for (n = 0; n < header_params; n++) { + if (&header[i] == ignored[n]) { + goto next_length; + } + } + if (params->number) { if (allocated < header[i].key.len) { allocated = header[i].key.len + 16; @@ -968,6 +980,15 @@ ngx_http_uwsgi_create_request(ngx_http_request_t *r) len += 2 + sizeof("HTTP_") - 1 + header[i].key.len + 2 + header[i].value.len; + + for (hn = header[i].next; hn; hn = hn->next) { + len += hn->value.len + 2; + ignored[header_params++] = hn; + } + + next_length: + + continue; } } @@ -1086,7 +1107,7 @@ ngx_http_uwsgi_create_request(ngx_http_request_t *r) for (n = 0; n < header_params; n++) { if (&header[i] == ignored[n]) { - goto next; + goto next_value; } } @@ -1109,15 +1130,41 @@ ngx_http_uwsgi_create_request(ngx_http_request_t *r) } val_len = header[i].value.len; + + for (hn = header[i].next; hn; hn = hn->next) { + val_len += hn->value.len + 2; + } + *b->last++ = (u_char) (val_len & 0xff); *b->last++ = (u_char) ((val_len >> 8) & 0xff); - b->last = ngx_copy(b->last, header[i].value.data, val_len); + b->last = ngx_copy(b->last, header[i].value.data, + header[i].value.len); + + if (header[i].next) { + + if (header[i].key.len == sizeof("Cookie") - 1 + && ngx_strncasecmp(header[i].key.data, (u_char *) "Cookie", + sizeof("Cookie") - 1) + == 0) + { + sep = ';'; + + } else { + sep = ','; + } + + for (hn = header[i].next; hn; hn = hn->next) { + *b->last++ = sep; + *b->last++ = ' '; + b->last = ngx_copy(b->last, hn->value.data, hn->value.len); + } + } ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "uwsgi param: \"%*s: %*s\"", key_len, b->last - (key_len + 2 + val_len), val_len, b->last - val_len); - next: + next_value: continue; } @@ -1295,8 +1342,12 @@ ngx_http_uwsgi_process_header(ngx_http_request_t *r) hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, h->lowcase_key, h->key.len); - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return NGX_ERROR; + if (hh) { + rc = hh->handler(r, h, hh->offset); + + if (rc != NGX_OK) { + return rc; + } } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -1330,7 +1381,10 @@ ngx_http_uwsgi_process_header(ngx_http_request_t *r) } u->headers_in.status_n = status; - u->headers_in.status_line = *status_line; + + if (status_line->len > 3) { + u->headers_in.status_line = *status_line; + } } else if (u->headers_in.location) { u->headers_in.status_n = 302; @@ -1816,12 +1870,17 @@ ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) #if (NGX_HTTP_SSL) + if (ngx_http_uwsgi_merge_ssl(cf, conf, prev) != NGX_OK) { + return NGX_CONF_ERROR; + } + ngx_conf_merge_value(conf->upstream.ssl_session_reuse, prev->upstream.ssl_session_reuse, 1); ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols, - (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 - |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); + (NGX_CONF_BITMASK_SET + |NGX_SSL_TLSv1|NGX_SSL_TLSv1_1 + |NGX_SSL_TLSv1_2|NGX_SSL_TLSv1_3)); ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT"); @@ -1878,7 +1937,7 @@ ngx_http_uwsgi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) conf->uwsgi_values = prev->uwsgi_values; #if (NGX_HTTP_SSL) - conf->upstream.ssl = prev->upstream.ssl; + conf->ssl = prev->ssl; #endif } @@ -2406,16 +2465,62 @@ ngx_http_uwsgi_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) static ngx_int_t -ngx_http_uwsgi_set_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *uwcf) +ngx_http_uwsgi_merge_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *conf, + ngx_http_uwsgi_loc_conf_t *prev) { - ngx_pool_cleanup_t *cln; + ngx_uint_t preserve; + + if (conf->ssl_protocols == 0 + && conf->ssl_ciphers.data == NULL + && conf->upstream.ssl_certificate == NGX_CONF_UNSET_PTR + && conf->upstream.ssl_certificate_key == NGX_CONF_UNSET_PTR + && conf->upstream.ssl_passwords == NGX_CONF_UNSET_PTR + && conf->upstream.ssl_verify == NGX_CONF_UNSET + && conf->ssl_verify_depth == NGX_CONF_UNSET_UINT + && conf->ssl_trusted_certificate.data == NULL + && conf->ssl_crl.data == NULL + && conf->upstream.ssl_session_reuse == NGX_CONF_UNSET + && conf->ssl_conf_commands == NGX_CONF_UNSET_PTR) + { + if (prev->upstream.ssl) { + conf->upstream.ssl = prev->upstream.ssl; + return NGX_OK; + } - uwcf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); - if (uwcf->upstream.ssl == NULL) { + preserve = 1; + + } else { + preserve = 0; + } + + conf->upstream.ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); + if (conf->upstream.ssl == NULL) { return NGX_ERROR; } - uwcf->upstream.ssl->log = cf->log; + conf->upstream.ssl->log = cf->log; + + /* + * special handling to preserve conf->upstream.ssl + * in the "http" section to inherit it to all servers + */ + + if (preserve) { + prev->upstream.ssl = conf->upstream.ssl; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_uwsgi_set_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *uwcf) +{ + ngx_pool_cleanup_t *cln; + + if (uwcf->upstream.ssl->ctx) { + return NGX_OK; + } if (ngx_ssl_create(uwcf->upstream.ssl, uwcf->ssl_protocols, NULL) != NGX_OK) @@ -2438,8 +2543,9 @@ ngx_http_uwsgi_set_ssl(ngx_conf_t *cf, ngx_http_uwsgi_loc_conf_t *uwcf) return NGX_ERROR; } - if (uwcf->upstream.ssl_certificate) { - + if (uwcf->upstream.ssl_certificate + && uwcf->upstream.ssl_certificate->value.len) + { if (uwcf->upstream.ssl_certificate_key == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"uwsgi_ssl_certificate_key\" is defined " diff --git a/src/http/modules/perl/nginx.xs b/src/http/modules/perl/nginx.xs index caf7c08..fd59e29 100644 --- a/src/http/modules/perl/nginx.xs +++ b/src/http/modules/perl/nginx.xs @@ -269,10 +269,9 @@ header_in(r, key) u_char *p, *lowcase_key, *value, sep; STRLEN len; ssize_t size; - ngx_uint_t i, n, hash; - ngx_array_t *a; + ngx_uint_t i, hash; ngx_list_part_t *part; - ngx_table_elt_t *h, **ph; + ngx_table_elt_t *h, *header, **ph; ngx_http_header_t *hh; ngx_http_core_main_conf_t *cmcf; @@ -302,78 +301,23 @@ header_in(r, key) if (hh) { - if (hh->offset == offsetof(ngx_http_headers_in_t, cookies)) { + if (hh->offset == offsetof(ngx_http_headers_in_t, cookie)) { sep = ';'; - goto multi; - } -#if (NGX_HTTP_X_FORWARDED_FOR) - if (hh->offset == offsetof(ngx_http_headers_in_t, x_forwarded_for)) { + + } else { sep = ','; - goto multi; } -#endif ph = (ngx_table_elt_t **) ((char *) &r->headers_in + hh->offset); - if (*ph) { - ngx_http_perl_set_targ((*ph)->value.data, (*ph)->value.len); - - goto done; - } - - XSRETURN_UNDEF; - - multi: - - /* Cookie, X-Forwarded-For */ - - a = (ngx_array_t *) ((char *) &r->headers_in + hh->offset); - - n = a->nelts; - - if (n == 0) { - XSRETURN_UNDEF; - } - - ph = a->elts; - - if (n == 1) { - ngx_http_perl_set_targ((*ph)->value.data, (*ph)->value.len); - - goto done; - } - - size = - (ssize_t) (sizeof("; ") - 1); - - for (i = 0; i < n; i++) { - size += ph[i]->value.len + sizeof("; ") - 1; - } - - value = ngx_pnalloc(r->pool, size); - if (value == NULL) { - ctx->error = 1; - croak("ngx_pnalloc() failed"); - } - - p = value; - - for (i = 0; /* void */ ; i++) { - p = ngx_copy(p, ph[i]->value.data, ph[i]->value.len); - - if (i == n - 1) { - break; - } - - *p++ = sep; *p++ = ' '; - } - - ngx_http_perl_set_targ(value, size); - - goto done; + goto found; } /* iterate over all headers */ + sep = ','; + ph = &header; + part = &r->headers_in.headers.part; h = part->elts; @@ -395,12 +339,49 @@ header_in(r, key) continue; } - ngx_http_perl_set_targ(h[i].value.data, h[i].value.len); + *ph = &h[i]; + ph = &h[i].next; + } + + *ph = NULL; + ph = &header; + + found: + + if (*ph == NULL) { + XSRETURN_UNDEF; + } + if ((*ph)->next == NULL) { + ngx_http_perl_set_targ((*ph)->value.data, (*ph)->value.len); goto done; } - XSRETURN_UNDEF; + size = - (ssize_t) (sizeof("; ") - 1); + + for (h = *ph; h; h = h->next) { + size += h->value.len + sizeof("; ") - 1; + } + + value = ngx_pnalloc(r->pool, size); + if (value == NULL) { + ctx->error = 1; + croak("ngx_pnalloc() failed"); + } + + p = value; + + for (h = *ph; h; h = h->next) { + p = ngx_copy(p, h->value.data, h->value.len); + + if (h->next == NULL) { + break; + } + + *p++ = sep; *p++ = ' '; + } + + ngx_http_perl_set_targ(value, size); done: @@ -591,6 +572,7 @@ header_out(r, key, value) } header->hash = 1; + header->next = NULL; if (ngx_http_perl_sv2str(aTHX_ r, &header->key, key) != NGX_OK) { header->hash = 0; diff --git a/src/http/ngx_http.c b/src/http/ngx_http.c index 73c08d5..d835f89 100644 --- a/src/http/ngx_http.c +++ b/src/http/ngx_http.c @@ -1130,7 +1130,7 @@ ngx_http_create_locations_tree(ngx_conf_t *cf, ngx_queue_t *locations, node->auto_redirect = (u_char) ((lq->exact && lq->exact->auto_redirect) || (lq->inclusive && lq->inclusive->auto_redirect)); - node->len = (u_char) len; + node->len = (u_short) len; ngx_memcpy(node->name, &lq->name->data[prefix], len); ngx_queue_split(locations, q, &tail); @@ -1200,7 +1200,10 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, port = cmcf->ports->elts; for (i = 0; i < cmcf->ports->nelts; i++) { - if (p != port[i].port || sa->sa_family != port[i].family) { + if (p != port[i].port + || lsopt->type != port[i].type + || sa->sa_family != port[i].family) + { continue; } @@ -1217,6 +1220,7 @@ ngx_http_add_listen(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, } port->family = sa->sa_family; + port->type = lsopt->type; port->port = p; port->addrs.elts = NULL; @@ -1228,7 +1232,8 @@ static ngx_int_t ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, ngx_http_conf_port_t *port, ngx_http_listen_opt_t *lsopt) { - ngx_uint_t i, default_server, proxy_protocol; + ngx_uint_t i, default_server, proxy_protocol, + protocols, protocols_prev; ngx_http_conf_addr_t *addr; #if (NGX_HTTP_SSL) ngx_uint_t ssl; @@ -1236,6 +1241,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_V2) ngx_uint_t http2; #endif +#if (NGX_HTTP_V3) + ngx_uint_t quic; +#endif /* * we cannot compare whole sockaddr struct's as kernel @@ -1264,12 +1272,21 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, default_server = addr[i].opt.default_server; proxy_protocol = lsopt->proxy_protocol || addr[i].opt.proxy_protocol; + protocols = lsopt->proxy_protocol; + protocols_prev = addr[i].opt.proxy_protocol; #if (NGX_HTTP_SSL) ssl = lsopt->ssl || addr[i].opt.ssl; + protocols |= lsopt->ssl << 1; + protocols_prev |= addr[i].opt.ssl << 1; #endif #if (NGX_HTTP_V2) http2 = lsopt->http2 || addr[i].opt.http2; + protocols |= lsopt->http2 << 2; + protocols_prev |= addr[i].opt.http2 << 2; +#endif +#if (NGX_HTTP_V3) + quic = lsopt->quic || addr[i].opt.quic; #endif if (lsopt->set) { @@ -1299,6 +1316,57 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, addr[i].default_server = cscf; } + /* check for conflicting protocol options */ + + if ((protocols | protocols_prev) != protocols_prev) { + + /* options added */ + + if ((addr[i].opt.set && !lsopt->set) + || addr[i].protocols_changed + || (protocols | protocols_prev) != protocols) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "protocol options redefined for %V", + &addr[i].opt.addr_text); + } + + addr[i].protocols = protocols_prev; + addr[i].protocols_set = 1; + addr[i].protocols_changed = 1; + + } else if ((protocols_prev | protocols) != protocols) { + + /* options removed */ + + if (lsopt->set + || (addr[i].protocols_set && protocols != addr[i].protocols)) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "protocol options redefined for %V", + &addr[i].opt.addr_text); + } + + addr[i].protocols = protocols; + addr[i].protocols_set = 1; + addr[i].protocols_changed = 1; + + } else { + + /* the same options */ + + if ((lsopt->set && addr[i].protocols_changed) + || (addr[i].protocols_set && protocols != addr[i].protocols)) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "protocol options redefined for %V", + &addr[i].opt.addr_text); + } + + addr[i].protocols = protocols; + addr[i].protocols_set = 1; + } + addr[i].opt.default_server = default_server; addr[i].opt.proxy_protocol = proxy_protocol; #if (NGX_HTTP_SSL) @@ -1307,6 +1375,9 @@ ngx_http_add_addresses(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, #if (NGX_HTTP_V2) addr[i].opt.http2 = http2; #endif +#if (NGX_HTTP_V3) + addr[i].opt.quic = quic; +#endif return NGX_OK; } @@ -1355,6 +1426,9 @@ ngx_http_add_address(ngx_conf_t *cf, ngx_http_core_srv_conf_t *cscf, } addr->opt = *lsopt; + addr->protocols = 0; + addr->protocols_set = 0; + addr->protocols_changed = 0; addr->hash.buckets = NULL; addr->hash.size = 0; addr->wc_head = NULL; @@ -1770,6 +1844,7 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) } #endif + ls->type = addr->opt.type; ls->backlog = addr->opt.backlog; ls->rcvbuf = addr->opt.rcvbuf; ls->sndbuf = addr->opt.sndbuf; @@ -1805,6 +1880,12 @@ ngx_http_add_listening(ngx_conf_t *cf, ngx_http_conf_addr_t *addr) ls->reuseport = addr->opt.reuseport; #endif + ls->wildcard = addr->opt.wildcard; + +#if (NGX_HTTP_V3) + ls->quic = addr->opt.quic; +#endif + return ls; } @@ -1836,6 +1917,9 @@ ngx_http_add_addrs(ngx_conf_t *cf, ngx_http_port_t *hport, #endif #if (NGX_HTTP_V2) addrs[i].conf.http2 = addr[i].opt.http2; +#endif +#if (NGX_HTTP_V3) + addrs[i].conf.quic = addr[i].opt.quic; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; @@ -1901,6 +1985,9 @@ ngx_http_add_addrs6(ngx_conf_t *cf, ngx_http_port_t *hport, #endif #if (NGX_HTTP_V2) addrs6[i].conf.http2 = addr[i].opt.http2; +#endif +#if (NGX_HTTP_V3) + addrs6[i].conf.quic = addr[i].opt.quic; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; diff --git a/src/http/ngx_http.h b/src/http/ngx_http.h index be8b7cd..e06464e 100644 --- a/src/http/ngx_http.h +++ b/src/http/ngx_http.h @@ -20,6 +20,8 @@ typedef struct ngx_http_file_cache_s ngx_http_file_cache_t; typedef struct ngx_http_log_ctx_s ngx_http_log_ctx_t; typedef struct ngx_http_chunked_s ngx_http_chunked_t; typedef struct ngx_http_v2_stream_s ngx_http_v2_stream_t; +typedef struct ngx_http_v3_parse_s ngx_http_v3_parse_t; +typedef struct ngx_http_v3_session_s ngx_http_v3_session_t; typedef ngx_int_t (*ngx_http_header_handler_pt)(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); @@ -38,6 +40,9 @@ typedef u_char *(*ngx_http_log_handler_pt)(ngx_http_request_t *r, #if (NGX_HTTP_V2) #include <ngx_http_v2.h> #endif +#if (NGX_HTTP_V3) +#include <ngx_http_v3.h> +#endif #if (NGX_HTTP_CACHE) #include <ngx_http_cache.h> #endif @@ -103,10 +108,10 @@ ngx_int_t ngx_http_parse_unsafe_uri(ngx_http_request_t *r, ngx_str_t *uri, ngx_str_t *args, ngx_uint_t *flags); ngx_int_t ngx_http_parse_header_line(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t allow_underscores); -ngx_int_t ngx_http_parse_multi_header_lines(ngx_array_t *headers, - ngx_str_t *name, ngx_str_t *value); -ngx_int_t ngx_http_parse_set_cookie_lines(ngx_array_t *headers, - ngx_str_t *name, ngx_str_t *value); +ngx_table_elt_t *ngx_http_parse_multi_header_lines(ngx_http_request_t *r, + ngx_table_elt_t *headers, ngx_str_t *name, ngx_str_t *value); +ngx_table_elt_t *ngx_http_parse_set_cookie_lines(ngx_http_request_t *r, + ngx_table_elt_t *headers, ngx_str_t *name, ngx_str_t *value); ngx_int_t ngx_http_arg(ngx_http_request_t *r, u_char *name, size_t len, ngx_str_t *value); void ngx_http_split_args(ngx_http_request_t *r, ngx_str_t *uri, @@ -124,6 +129,11 @@ void ngx_http_handler(ngx_http_request_t *r); void ngx_http_run_posted_requests(ngx_connection_t *c); ngx_int_t ngx_http_post_request(ngx_http_request_t *r, ngx_http_posted_request_t *pr); +ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, + ngx_str_t *host); +ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, + ngx_uint_t alloc); +void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc); void ngx_http_finalize_request(ngx_http_request_t *r, ngx_int_t rc); void ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc); @@ -167,7 +177,7 @@ ngx_uint_t ngx_http_degraded(ngx_http_request_t *); #endif -#if (NGX_HTTP_V2) +#if (NGX_HTTP_V2 || NGX_HTTP_V3) ngx_int_t ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, ngx_uint_t last, ngx_log_t *log); size_t ngx_http_huff_encode(u_char *src, size_t len, u_char *dst, diff --git a/src/http/ngx_http_copy_filter_module.c b/src/http/ngx_http_copy_filter_module.c index bd3028b..8e5de2b 100644 --- a/src/http/ngx_http_copy_filter_module.c +++ b/src/http/ngx_http_copy_filter_module.c @@ -170,6 +170,8 @@ ngx_http_copy_aio_handler(ngx_output_chain_ctx_t *ctx, ngx_file_t *file) file->aio->data = r; file->aio->handler = ngx_http_copy_aio_event_handler; + ngx_add_timer(&file->aio->event, 60000); + r->main->blocked++; r->aio = 1; ctx->aio = 1; @@ -192,12 +194,32 @@ ngx_http_copy_aio_event_handler(ngx_event_t *ev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http aio: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "aio operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; - r->write_event_handler(r); + if (r->main->terminated) { + /* + * trigger connection event handler if the request was + * terminated + */ + + c->write->handler(c->write); - ngx_http_run_posted_requests(c); + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } } #endif @@ -264,6 +286,8 @@ ngx_http_copy_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) return NGX_ERROR; } + ngx_add_timer(&task->event, 60000); + r->main->blocked++; r->aio = 1; @@ -288,6 +312,17 @@ ngx_http_copy_thread_event_handler(ngx_event_t *ev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http thread: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "thread operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; @@ -305,11 +340,11 @@ ngx_http_copy_thread_event_handler(ngx_event_t *ev) #endif - if (r->done) { + if (r->done || r->main->terminated) { /* * trigger connection event handler if the subrequest was - * already finalized; this can happen if the handler is used - * for sendfile() in threads + * already finalized (this can happen if the handler is used + * for sendfile() in threads), or if the request was terminated */ c->write->handler(c->write); diff --git a/src/http/ngx_http_core_module.c b/src/http/ngx_http_core_module.c index c7463dc..033a3bf 100644 --- a/src/http/ngx_http_core_module.c +++ b/src/http/ngx_http_core_module.c @@ -1007,6 +1007,7 @@ ngx_http_core_find_config_phase(ngx_http_request_t *r, } r->headers_out.location->hash = 1; + r->headers_out.location->next = NULL; ngx_str_set(&r->headers_out.location->key, "Location"); if (r->args.len == 0) { @@ -1087,6 +1088,7 @@ ngx_int_t ngx_http_core_access_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph) { ngx_int_t rc; + ngx_table_elt_t *h; ngx_http_core_loc_conf_t *clcf; if (r != r->main) { @@ -1121,8 +1123,8 @@ ngx_http_core_access_phase(ngx_http_request_t *r, ngx_http_phase_handler_t *ph) if (rc == NGX_OK) { r->access_code = 0; - if (r->headers_out.www_authenticate) { - r->headers_out.www_authenticate->hash = 0; + for (h = r->headers_out.www_authenticate; h; h = h->next) { + h->hash = 0; } r->phase_handler = ph->next; @@ -1687,6 +1689,7 @@ ngx_http_set_etag(ngx_http_request_t *r) } etag->hash = 1; + etag->next = NULL; ngx_str_set(&etag->key, "ETag"); etag->value.data = ngx_pnalloc(r->pool, NGX_OFF_T_LEN + NGX_TIME_T_LEN + 3); @@ -1781,6 +1784,7 @@ ngx_http_send_response(ngx_http_request_t *r, ngx_uint_t status, } r->headers_out.location->hash = 1; + r->headers_out.location->next = NULL; ngx_str_set(&r->headers_out.location->key, "Location"); r->headers_out.location->value = val; @@ -1799,10 +1803,6 @@ ngx_http_send_response(ngx_http_request_t *r, ngx_uint_t status, } } - if (r != r->main && val.len == 0) { - return ngx_http_send_header(r); - } - b = ngx_calloc_buf(r->pool); if (b == NULL) { return NGX_HTTP_INTERNAL_SERVER_ERROR; @@ -1813,6 +1813,7 @@ ngx_http_send_response(ngx_http_request_t *r, ngx_uint_t status, b->memory = val.len ? 1 : 0; b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; + b->sync = (b->last_buf || b->memory) ? 0 : 1; out.buf = b; out.next = NULL; @@ -2024,8 +2025,7 @@ ngx_http_gzip_ok(ngx_http_request_t *r) { time_t date, expires; ngx_uint_t p; - ngx_array_t *cc; - ngx_table_elt_t *e, *d, *ae; + ngx_table_elt_t *e, *d, *ae, *cc; ngx_http_core_loc_conf_t *clcf; r->gzip_tested = 1; @@ -2118,30 +2118,30 @@ ngx_http_gzip_ok(ngx_http_request_t *r) return NGX_DECLINED; } - cc = &r->headers_out.cache_control; + cc = r->headers_out.cache_control; - if (cc->elts) { + if (cc) { if ((p & NGX_HTTP_GZIP_PROXIED_NO_CACHE) - && ngx_http_parse_multi_header_lines(cc, &ngx_http_gzip_no_cache, + && ngx_http_parse_multi_header_lines(r, cc, &ngx_http_gzip_no_cache, NULL) - >= 0) + != NULL) { goto ok; } if ((p & NGX_HTTP_GZIP_PROXIED_NO_STORE) - && ngx_http_parse_multi_header_lines(cc, &ngx_http_gzip_no_store, + && ngx_http_parse_multi_header_lines(r, cc, &ngx_http_gzip_no_store, NULL) - >= 0) + != NULL) { goto ok; } if ((p & NGX_HTTP_GZIP_PROXIED_PRIVATE) - && ngx_http_parse_multi_header_lines(cc, &ngx_http_gzip_private, + && ngx_http_parse_multi_header_lines(r, cc, &ngx_http_gzip_private, NULL) - >= 0) + != NULL) { goto ok; } @@ -2712,12 +2712,12 @@ ngx_http_set_disable_symlinks(ngx_http_request_t *r, ngx_int_t ngx_http_get_forwarded_addr(ngx_http_request_t *r, ngx_addr_t *addr, - ngx_array_t *headers, ngx_str_t *value, ngx_array_t *proxies, + ngx_table_elt_t *headers, ngx_str_t *value, ngx_array_t *proxies, int recursive) { - ngx_int_t rc; - ngx_uint_t i, found; - ngx_table_elt_t **h; + ngx_int_t rc; + ngx_uint_t found; + ngx_table_elt_t *h, *next; if (headers == NULL) { return ngx_http_get_forwarded_addr_internal(r, addr, value->data, @@ -2725,16 +2725,23 @@ ngx_http_get_forwarded_addr(ngx_http_request_t *r, ngx_addr_t *addr, recursive); } - i = headers->nelts; - h = headers->elts; + /* revert headers order */ + + for (h = headers, headers = NULL; h; h = next) { + next = h->next; + h->next = headers; + headers = h; + } + + /* iterate over all headers in reverse order */ rc = NGX_DECLINED; found = 0; - while (i-- > 0) { - rc = ngx_http_get_forwarded_addr_internal(r, addr, h[i]->value.data, - h[i]->value.len, proxies, + for (h = headers; h; h = h->next) { + rc = ngx_http_get_forwarded_addr_internal(r, addr, h->value.data, + h->value.len, proxies, recursive); if (!recursive) { @@ -2753,6 +2760,14 @@ ngx_http_get_forwarded_addr(ngx_http_request_t *r, ngx_addr_t *addr, found = 1; } + /* restore headers order */ + + for (h = headers, headers = NULL; h; h = next) { + next = h->next; + h->next = headers; + headers = h; + } + return rc; } @@ -2802,6 +2817,80 @@ ngx_http_get_forwarded_addr_internal(ngx_http_request_t *r, ngx_addr_t *addr, } +ngx_int_t +ngx_http_link_multi_headers(ngx_http_request_t *r) +{ + ngx_uint_t i, j; + ngx_list_part_t *part, *ppart; + ngx_table_elt_t *header, *pheader, **ph; + + if (r->headers_in.multi_linked) { + return NGX_OK; + } + + r->headers_in.multi_linked = 1; + + part = &r->headers_in.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + header[i].next = NULL; + + /* + * search for previous headers with the same name; + * if there are any, link to them + */ + + ppart = &r->headers_in.headers.part; + pheader = ppart->elts; + + for (j = 0; /* void */; j++) { + + if (j >= ppart->nelts) { + if (ppart->next == NULL) { + break; + } + + ppart = ppart->next; + pheader = ppart->elts; + j = 0; + } + + if (part == ppart && i == j) { + break; + } + + if (header[i].key.len == pheader[j].key.len + && ngx_strncasecmp(header[i].key.data, pheader[j].key.data, + header[i].key.len) + == 0) + { + ph = &pheader[j].next; + while (*ph) { ph = &(*ph)->next; } + *ph = &header[i]; + + r->headers_in.multi = 1; + + break; + } + } + } + + return NGX_OK; +} + + static char * ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) { @@ -2916,6 +3005,7 @@ ngx_http_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *dummy) lsopt.socklen = sizeof(struct sockaddr_in); lsopt.backlog = NGX_LISTEN_BACKLOG; + lsopt.type = SOCK_STREAM; lsopt.rcvbuf = -1; lsopt.sndbuf = -1; #if (NGX_HAVE_SETFIB) @@ -3871,7 +3961,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value, size; ngx_url_t u; - ngx_uint_t n; + ngx_uint_t n, i, backlog; ngx_http_listen_opt_t lsopt; cscf->listen = 1; @@ -3897,6 +3987,7 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_memzero(&lsopt, sizeof(ngx_http_listen_opt_t)); lsopt.backlog = NGX_LISTEN_BACKLOG; + lsopt.type = SOCK_STREAM; lsopt.rcvbuf = -1; lsopt.sndbuf = -1; #if (NGX_HAVE_SETFIB) @@ -3909,6 +4000,8 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) lsopt.ipv6only = 1; #endif + backlog = 0; + for (n = 2; n < cf->args->nelts; n++) { if (ngx_strcmp(value[n].data, "default_server") == 0 @@ -3967,6 +4060,8 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } + backlog = 1; + continue; } @@ -4085,6 +4180,11 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (ngx_strcmp(value[n].data, "http2") == 0) { #if (NGX_HTTP_V2) + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "the \"listen ... http2\" directive " + "is deprecated, use " + "the \"http2\" directive instead"); + lsopt.http2 = 1; continue; #else @@ -4095,6 +4195,19 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } + if (ngx_strcmp(value[n].data, "quic") == 0) { +#if (NGX_HTTP_V3) + lsopt.quic = 1; + lsopt.type = SOCK_DGRAM; + continue; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the \"quic\" parameter requires " + "ngx_http_v3_module"); + return NGX_CONF_ERROR; +#endif + } + if (ngx_strncmp(value[n].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[n].data[13], "on") == 0) { @@ -4196,7 +4309,61 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } + if (lsopt.quic) { +#if (NGX_HAVE_TCP_FASTOPEN) + if (lsopt.fastopen != -1) { + return "\"fastopen\" parameter is incompatible with \"quic\""; + } +#endif + + if (backlog) { + return "\"backlog\" parameter is incompatible with \"quic\""; + } + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + if (lsopt.accept_filter) { + return "\"accept_filter\" parameter is incompatible with \"quic\""; + } +#endif + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) + if (lsopt.deferred_accept) { + return "\"deferred\" parameter is incompatible with \"quic\""; + } +#endif + +#if (NGX_HTTP_SSL) + if (lsopt.ssl) { + return "\"ssl\" parameter is incompatible with \"quic\""; + } +#endif + +#if (NGX_HTTP_V2) + if (lsopt.http2) { + return "\"http2\" parameter is incompatible with \"quic\""; + } +#endif + + if (lsopt.so_keepalive) { + return "\"so_keepalive\" parameter is incompatible with \"quic\""; + } + + if (lsopt.proxy_protocol) { + return "\"proxy_protocol\" parameter is incompatible with \"quic\""; + } + } + for (n = 0; n < u.naddrs; n++) { + + for (i = 0; i < n; i++) { + if (ngx_cmp_sockaddr(u.addrs[n].sockaddr, u.addrs[n].socklen, + u.addrs[i].sockaddr, u.addrs[i].socklen, 1) + == NGX_OK) + { + goto next; + } + } + lsopt.sockaddr = u.addrs[n].sockaddr; lsopt.socklen = u.addrs[n].socklen; lsopt.addr_text = u.addrs[n].name; @@ -4205,6 +4372,9 @@ ngx_http_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (ngx_http_add_listen(cf, cscf, &lsopt) != NGX_OK) { return NGX_CONF_ERROR; } + + next: + continue; } return NGX_CONF_OK; diff --git a/src/http/ngx_http_core_module.h b/src/http/ngx_http_core_module.h index 004a98e..765e7ff 100644 --- a/src/http/ngx_http_core_module.h +++ b/src/http/ngx_http_core_module.h @@ -75,6 +75,7 @@ typedef struct { unsigned wildcard:1; unsigned ssl:1; unsigned http2:1; + unsigned quic:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif @@ -86,6 +87,7 @@ typedef struct { int backlog; int rcvbuf; int sndbuf; + int type; #if (NGX_HAVE_SETFIB) int setfib; #endif @@ -237,6 +239,7 @@ struct ngx_http_addr_conf_s { unsigned ssl:1; unsigned http2:1; + unsigned quic:1; unsigned proxy_protocol:1; }; @@ -266,6 +269,7 @@ typedef struct { typedef struct { ngx_int_t family; + ngx_int_t type; in_port_t port; ngx_array_t addrs; /* array of ngx_http_conf_addr_t */ } ngx_http_conf_port_t; @@ -274,6 +278,10 @@ typedef struct { typedef struct { ngx_http_listen_opt_t opt; + unsigned protocols:3; + unsigned protocols_set:1; + unsigned protocols_changed:1; + ngx_hash_t hash; ngx_hash_wildcard_t *wc_head; ngx_hash_wildcard_t *wc_tail; @@ -463,8 +471,8 @@ struct ngx_http_location_tree_node_s { ngx_http_core_loc_conf_t *exact; ngx_http_core_loc_conf_t *inclusive; + u_short len; u_char auto_redirect; - u_char len; u_char name[1]; }; @@ -529,9 +537,11 @@ ngx_int_t ngx_http_set_disable_symlinks(ngx_http_request_t *r, ngx_http_core_loc_conf_t *clcf, ngx_str_t *path, ngx_open_file_info_t *of); ngx_int_t ngx_http_get_forwarded_addr(ngx_http_request_t *r, ngx_addr_t *addr, - ngx_array_t *headers, ngx_str_t *value, ngx_array_t *proxies, + ngx_table_elt_t *headers, ngx_str_t *value, ngx_array_t *proxies, int recursive); +ngx_int_t ngx_http_link_multi_headers(ngx_http_request_t *r); + extern ngx_module_t ngx_http_core_module; diff --git a/src/http/ngx_http_file_cache.c b/src/http/ngx_http_file_cache.c index c40093b..5209f00 100644 --- a/src/http/ngx_http_file_cache.c +++ b/src/http/ngx_http_file_cache.c @@ -14,7 +14,7 @@ static ngx_int_t ngx_http_file_cache_lock(ngx_http_request_t *r, ngx_http_cache_t *c); static void ngx_http_file_cache_lock_wait_handler(ngx_event_t *ev); -static void ngx_http_file_cache_lock_wait(ngx_http_request_t *r, +static ngx_int_t ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c); static ngx_int_t ngx_http_file_cache_read(ngx_http_request_t *r, ngx_http_cache_t *c); @@ -463,6 +463,7 @@ ngx_http_file_cache_lock(ngx_http_request_t *r, ngx_http_cache_t *c) static void ngx_http_file_cache_lock_wait_handler(ngx_event_t *ev) { + ngx_int_t rc; ngx_connection_t *c; ngx_http_request_t *r; @@ -474,13 +475,31 @@ ngx_http_file_cache_lock_wait_handler(ngx_event_t *ev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http file cache wait: \"%V?%V\"", &r->uri, &r->args); - ngx_http_file_cache_lock_wait(r, r->cache); + rc = ngx_http_file_cache_lock_wait(r, r->cache); - ngx_http_run_posted_requests(c); + if (rc == NGX_AGAIN) { + return; + } + + r->cache->waiting = 0; + r->main->blocked--; + + if (r->main->terminated) { + /* + * trigger connection event handler if the request was + * terminated + */ + + c->write->handler(c->write); + + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } } -static void +static ngx_int_t ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c) { ngx_uint_t wait; @@ -495,7 +514,7 @@ ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c) ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, "cache lock timeout"); c->lock_timeout = 0; - goto wakeup; + return NGX_OK; } cache = c->file_cache; @@ -513,14 +532,10 @@ ngx_http_file_cache_lock_wait(ngx_http_request_t *r, ngx_http_cache_t *c) if (wait) { ngx_add_timer(&c->wait_event, (timer > 500) ? 500 : timer); - return; + return NGX_AGAIN; } -wakeup: - - c->waiting = 0; - r->main->blocked--; - r->write_event_handler(r); + return NGX_OK; } @@ -690,6 +705,8 @@ ngx_http_file_cache_aio_read(ngx_http_request_t *r, ngx_http_cache_t *c) c->file.aio->data = r; c->file.aio->handler = ngx_http_cache_aio_event_handler; + ngx_add_timer(&c->file.aio->event, 60000); + r->main->blocked++; r->aio = 1; @@ -737,12 +754,32 @@ ngx_http_cache_aio_event_handler(ngx_event_t *ev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http file cache aio: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "aio operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; - r->write_event_handler(r); + if (r->main->terminated) { + /* + * trigger connection event handler if the request was + * terminated + */ + + c->write->handler(c->write); - ngx_http_run_posted_requests(c); + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } } #endif @@ -786,6 +823,8 @@ ngx_http_cache_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) return NGX_ERROR; } + ngx_add_timer(&task->event, 60000); + r->main->blocked++; r->aio = 1; @@ -807,12 +846,32 @@ ngx_http_cache_thread_event_handler(ngx_event_t *ev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http file cache thread: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "thread operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; - r->write_event_handler(r); + if (r->main->terminated) { + /* + * trigger connection event handler if the request was + * terminated + */ + + c->write->handler(c->write); - ngx_http_run_posted_requests(c); + } else { + r->write_event_handler(r); + ngx_http_run_posted_requests(c); + } } #endif @@ -1575,10 +1634,6 @@ ngx_http_cache_send(ngx_http_request_t *r) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http file cache send: %s", c->file.name.data); - if (r != r->main && c->length - c->body_start == 0) { - return ngx_http_send_header(r); - } - /* we need to allocate all before the header would be sent */ b = ngx_calloc_buf(r->pool); @@ -1600,9 +1655,10 @@ ngx_http_cache_send(ngx_http_request_t *r) b->file_pos = c->body_start; b->file_last = c->length; - b->in_file = (c->length - c->body_start) ? 1: 0; - b->last_buf = (r == r->main) ? 1: 0; + b->in_file = (c->length - c->body_start) ? 1 : 0; + b->last_buf = (r == r->main) ? 1 : 0; b->last_in_chain = 1; + b->sync = (b->last_buf || b->in_file) ? 0 : 1; b->file->fd = c->file.fd; b->file->name = c->file.name; @@ -1756,6 +1812,11 @@ ngx_http_file_cache_forced_expire(ngx_http_file_cache_t *cache) break; } + if (fcn->deleting) { + wait = 1; + break; + } + p = ngx_hex_dump(key, (u_char *) &fcn->node.key, sizeof(ngx_rbtree_key_t)); len = NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t); diff --git a/src/http/ngx_http_huff_decode.c b/src/http/ngx_http_huff_decode.c index 14b7b78..a65c3c3 100644 --- a/src/http/ngx_http_huff_decode.c +++ b/src/http/ngx_http_huff_decode.c @@ -2657,7 +2657,7 @@ ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, != NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, - "http2 huffman decoding error at state %d: " + "http huffman decoding error at state %d: " "bad code 0x%Xd", *state, ch >> 4); return NGX_ERROR; @@ -2667,7 +2667,7 @@ ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, != NGX_OK) { ngx_log_debug2(NGX_LOG_DEBUG_HTTP, log, 0, - "http2 huffman decoding error at state %d: " + "http huffman decoding error at state %d: " "bad code 0x%Xd", *state, ch & 0xf); return NGX_ERROR; @@ -2677,7 +2677,7 @@ ngx_http_huff_decode(u_char *state, u_char *src, size_t len, u_char **dst, if (last) { if (!ending) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, log, 0, - "http2 huffman decoding error: " + "http huffman decoding error: " "incomplete code 0x%Xd", ch); return NGX_ERROR; diff --git a/src/http/ngx_http_parse.c b/src/http/ngx_http_parse.c index 6460da2..f7e5038 100644 --- a/src/http/ngx_http_parse.c +++ b/src/http/ngx_http_parse.c @@ -451,19 +451,16 @@ ngx_http_parse_request_line(ngx_http_request_t *r, ngx_buf_t *b) switch (ch) { case '/': - r->port_end = p; r->uri_start = p; state = sw_after_slash_in_uri; break; case '?': - r->port_end = p; r->uri_start = p; r->args_start = p + 1; r->empty_path_in_uri = 1; state = sw_uri; break; case ' ': - r->port_end = p; /* * use single "/" from request line to preserve pointers, * if request line will be copied to large client buffer @@ -1960,27 +1957,24 @@ unsafe: } -ngx_int_t -ngx_http_parse_multi_header_lines(ngx_array_t *headers, ngx_str_t *name, - ngx_str_t *value) +ngx_table_elt_t * +ngx_http_parse_multi_header_lines(ngx_http_request_t *r, + ngx_table_elt_t *headers, ngx_str_t *name, ngx_str_t *value) { - ngx_uint_t i; - u_char *start, *last, *end, ch; - ngx_table_elt_t **h; - - h = headers->elts; + u_char *start, *last, *end, ch; + ngx_table_elt_t *h; - for (i = 0; i < headers->nelts; i++) { + for (h = headers; h; h = h->next) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, headers->pool->log, 0, - "parse header: \"%V: %V\"", &h[i]->key, &h[i]->value); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "parse header: \"%V: %V\"", &h->key, &h->value); - if (name->len > h[i]->value.len) { + if (name->len > h->value.len) { continue; } - start = h[i]->value.data; - end = h[i]->value.data + h[i]->value.len; + start = h->value.data; + end = h->value.data + h->value.len; while (start < end) { @@ -1994,7 +1988,7 @@ ngx_http_parse_multi_header_lines(ngx_array_t *headers, ngx_str_t *name, if (value == NULL) { if (start == end || *start == ',') { - return i; + return h; } goto skip; @@ -2014,7 +2008,7 @@ ngx_http_parse_multi_header_lines(ngx_array_t *headers, ngx_str_t *name, value->len = last - start; value->data = start; - return i; + return h; skip: @@ -2029,31 +2023,28 @@ ngx_http_parse_multi_header_lines(ngx_array_t *headers, ngx_str_t *name, } } - return NGX_DECLINED; + return NULL; } -ngx_int_t -ngx_http_parse_set_cookie_lines(ngx_array_t *headers, ngx_str_t *name, - ngx_str_t *value) +ngx_table_elt_t * +ngx_http_parse_set_cookie_lines(ngx_http_request_t *r, + ngx_table_elt_t *headers, ngx_str_t *name, ngx_str_t *value) { - ngx_uint_t i; - u_char *start, *last, *end; - ngx_table_elt_t **h; - - h = headers->elts; + u_char *start, *last, *end; + ngx_table_elt_t *h; - for (i = 0; i < headers->nelts; i++) { + for (h = headers; h; h = h->next) { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, headers->pool->log, 0, - "parse header: \"%V: %V\"", &h[i]->key, &h[i]->value); + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "parse header: \"%V: %V\"", &h->key, &h->value); - if (name->len >= h[i]->value.len) { + if (name->len >= h->value.len) { continue; } - start = h[i]->value.data; - end = h[i]->value.data + h[i]->value.len; + start = h->value.data; + end = h->value.data + h->value.len; if (ngx_strncasecmp(start, name->data, name->len) != 0) { continue; @@ -2077,10 +2068,10 @@ ngx_http_parse_set_cookie_lines(ngx_array_t *headers, ngx_str_t *name, value->len = last - start; value->data = start; - return i; + return h; } - return NGX_DECLINED; + return NULL; } diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c index 013b715..9593b7f 100644 --- a/src/http/ngx_http_request.c +++ b/src/http/ngx_http_request.c @@ -22,8 +22,6 @@ static ngx_int_t ngx_http_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_process_unique_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); -static ngx_int_t ngx_http_process_multi_header_lines(ngx_http_request_t *r, - ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_process_host(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r, @@ -31,10 +29,6 @@ static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r, static ngx_int_t ngx_http_process_user_agent(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); -static ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, - ngx_uint_t alloc); -static ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, - ngx_str_t *host); static ngx_int_t ngx_http_find_virtual_server(ngx_connection_t *c, ngx_http_virtual_names_t *virtual_names, ngx_str_t *host, ngx_http_request_t *r, ngx_http_core_srv_conf_t **cscfp); @@ -52,7 +46,6 @@ static void ngx_http_keepalive_handler(ngx_event_t *ev); static void ngx_http_set_lingering_close(ngx_connection_t *c); static void ngx_http_lingering_close_handler(ngx_event_t *ev); static ngx_int_t ngx_http_post_action(ngx_http_request_t *r); -static void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t error); static void ngx_http_log_request(ngx_http_request_t *r); static u_char *ngx_http_log_error(ngx_log_t *log, u_char *buf, size_t len); @@ -164,7 +157,7 @@ ngx_http_header_t ngx_http_headers_in[] = { #if (NGX_HTTP_X_FORWARDED_FOR) { ngx_string("X-Forwarded-For"), offsetof(ngx_http_headers_in_t, x_forwarded_for), - ngx_http_process_multi_header_lines }, + ngx_http_process_header_line }, #endif #if (NGX_HTTP_REALIP) @@ -196,8 +189,8 @@ ngx_http_header_t ngx_http_headers_in[] = { ngx_http_process_header_line }, #endif - { ngx_string("Cookie"), offsetof(ngx_http_headers_in_t, cookies), - ngx_http_process_multi_header_lines }, + { ngx_string("Cookie"), offsetof(ngx_http_headers_in_t, cookie), + ngx_http_process_header_line }, { ngx_null_string, 0, NULL } }; @@ -325,24 +318,19 @@ ngx_http_init_connection(ngx_connection_t *c) rev->handler = ngx_http_wait_request_handler; c->write->handler = ngx_http_empty_handler; -#if (NGX_HTTP_V2) - if (hc->addr_conf->http2) { - rev->handler = ngx_http_v2_init; +#if (NGX_HTTP_V3) + if (hc->addr_conf->quic) { + ngx_http_v3_init_stream(c); + return; } #endif #if (NGX_HTTP_SSL) - { - ngx_http_ssl_srv_conf_t *sscf; - - sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); - - if (sscf->enable || hc->addr_conf->ssl) { + if (hc->addr_conf->ssl) { hc->ssl = 1; c->log->action = "SSL handshaking"; rev->handler = ngx_http_ssl_handshake; } - } #endif if (hc->addr_conf->proxy_protocol) { @@ -383,6 +371,9 @@ ngx_http_wait_request_handler(ngx_event_t *rev) ngx_buf_t *b; ngx_connection_t *c; ngx_http_connection_t *hc; +#if (NGX_HTTP_V2) + ngx_http_v2_srv_conf_t *h2scf; +#endif ngx_http_core_srv_conf_t *cscf; c = rev->data; @@ -429,6 +420,8 @@ ngx_http_wait_request_handler(ngx_event_t *rev) b->end = b->last + size; } + size = b->end - b->last; + n = c->recv(c, b->last, size); if (n == NGX_AGAIN) { @@ -443,12 +436,16 @@ ngx_http_wait_request_handler(ngx_event_t *rev) return; } - /* - * We are trying to not hold c->buffer's memory for an idle connection. - */ + if (b->pos == b->last) { - if (ngx_pfree(c->pool, b->start) == NGX_OK) { - b->start = NULL; + /* + * We are trying to not hold c->buffer's memory for an + * idle connection. + */ + + if (ngx_pfree(c->pool, b->start) == NGX_OK) { + b->start = NULL; + } } return; @@ -489,6 +486,29 @@ ngx_http_wait_request_handler(ngx_event_t *rev) } } +#if (NGX_HTTP_V2) + + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + if (!hc->ssl && (h2scf->enable || hc->addr_conf->http2)) { + + size = ngx_min(sizeof(NGX_HTTP_V2_PREFACE) - 1, + (size_t) (b->last - b->pos)); + + if (ngx_memcmp(b->pos, NGX_HTTP_V2_PREFACE, size) == 0) { + + if (size == sizeof(NGX_HTTP_V2_PREFACE) - 1) { + ngx_http_v2_init(rev); + return; + } + + ngx_post_event(rev, &ngx_posted_events); + return; + } + } + +#endif + c->log->action = "reading client request line"; ngx_reusable_connection(c, 0); @@ -808,13 +828,16 @@ ngx_http_ssl_handshake_handler(ngx_connection_t *c) #if (NGX_HTTP_V2 \ && defined TLSEXT_TYPE_application_layer_protocol_negotiation) { - unsigned int len; - const unsigned char *data; - ngx_http_connection_t *hc; + unsigned int len; + const unsigned char *data; + ngx_http_connection_t *hc; + ngx_http_v2_srv_conf_t *h2scf; hc = c->data; - if (hc->addr_conf->http2) { + h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); + + if (h2scf->enable || hc->addr_conf->http2) { SSL_get0_alpn_selected(c->ssl->connection, &data, &len); @@ -909,6 +932,31 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) goto done; } + sscf = ngx_http_get_module_srv_conf(cscf->ctx, ngx_http_ssl_module); + +#if (defined TLS1_3_VERSION \ + && !defined LIBRESSL_VERSION_NUMBER && !defined OPENSSL_IS_BORINGSSL) + + /* + * SSL_SESSION_get0_hostname() is only available in OpenSSL 1.1.1+, + * but servername being negotiated in every TLSv1.3 handshake + * is only returned in OpenSSL 1.1.1+ as well + */ + + if (sscf->verify) { + const char *hostname; + + hostname = SSL_SESSION_get0_hostname(SSL_get0_session(ssl_conn)); + + if (hostname != NULL && ngx_strcmp(hostname, servername) != 0) { + c->ssl->handshake_rejected = 1; + *ad = SSL_AD_ACCESS_DENIED; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + } + +#endif + hc->ssl_servername = ngx_palloc(c->pool, sizeof(ngx_str_t)); if (hc->ssl_servername == NULL) { goto error; @@ -922,8 +970,6 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) ngx_set_connection_log(c, clcf->error_log); - sscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_ssl_module); - c->ssl->buffer_size = sscf->buffer_size; if (sscf->ssl.ctx) { @@ -951,6 +997,14 @@ ngx_http_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) #ifdef SSL_OP_NO_RENEGOTIATION SSL_set_options(ssl_conn, SSL_OP_NO_RENEGOTIATION); +#endif + +#ifdef SSL_OP_ENABLE_MIDDLEBOX_COMPAT +#if (NGX_HTTP_V3) + if (c->listening->quic) { + SSL_clear_options(ssl_conn, SSL_OP_ENABLE_MIDDLEBOX_COMPAT); + } +#endif #endif } @@ -1687,14 +1741,23 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, r->request_end = new + (r->request_end - old); } - r->method_end = new + (r->method_end - old); + if (r->method_end) { + r->method_end = new + (r->method_end - old); + } + + if (r->uri_start) { + r->uri_start = new + (r->uri_start - old); + } - r->uri_start = new + (r->uri_start - old); - r->uri_end = new + (r->uri_end - old); + if (r->uri_end) { + r->uri_end = new + (r->uri_end - old); + } if (r->schema_start) { r->schema_start = new + (r->schema_start - old); - r->schema_end = new + (r->schema_end - old); + if (r->schema_end) { + r->schema_end = new + (r->schema_end - old); + } } if (r->host_start) { @@ -1704,11 +1767,6 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, } } - if (r->port_start) { - r->port_start = new + (r->port_start - old); - r->port_end = new + (r->port_end - old); - } - if (r->uri_ext) { r->uri_ext = new + (r->uri_ext - old); } @@ -1723,9 +1781,18 @@ ngx_http_alloc_large_header_buffer(ngx_http_request_t *r, } else { r->header_name_start = new; - r->header_name_end = new + (r->header_name_end - old); - r->header_start = new + (r->header_start - old); - r->header_end = new + (r->header_end - old); + + if (r->header_name_end) { + r->header_name_end = new + (r->header_name_end - old); + } + + if (r->header_start) { + r->header_start = new + (r->header_start - old); + } + + if (r->header_end) { + r->header_end = new + (r->header_end - old); + } } r->header_in = b; @@ -1742,9 +1809,10 @@ ngx_http_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ph = (ngx_table_elt_t **) ((char *) &r->headers_in + offset); - if (*ph == NULL) { - *ph = h; - } + while (*ph) { ph = &(*ph)->next; } + + *ph = h; + h->next = NULL; return NGX_OK; } @@ -1760,6 +1828,7 @@ ngx_http_process_unique_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, if (*ph == NULL) { *ph = h; + h->next = NULL; return NGX_OK; } @@ -1792,6 +1861,7 @@ ngx_http_process_host(ngx_http_request_t *r, ngx_table_elt_t *h, } r->headers_in.host = h; + h->next = NULL; host = h->value; @@ -1827,6 +1897,10 @@ static ngx_int_t ngx_http_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { + if (ngx_http_process_header_line(r, h, offset) != NGX_OK) { + return NGX_ERROR; + } + if (ngx_strcasestrn(h->value.data, "close", 5 - 1)) { r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; @@ -1844,12 +1918,10 @@ ngx_http_process_user_agent(ngx_http_request_t *r, ngx_table_elt_t *h, { u_char *user_agent, *msie; - if (r->headers_in.user_agent) { - return NGX_OK; + if (ngx_http_process_header_line(r, h, offset) != NGX_OK) { + return NGX_ERROR; } - r->headers_in.user_agent = h; - /* check some widespread browsers while the header is in CPU cache */ user_agent = h->value.data; @@ -1911,35 +1983,6 @@ ngx_http_process_user_agent(ngx_http_request_t *r, ngx_table_elt_t *h, } -static ngx_int_t -ngx_http_process_multi_header_lines(ngx_http_request_t *r, ngx_table_elt_t *h, - ngx_uint_t offset) -{ - ngx_array_t *headers; - ngx_table_elt_t **ph; - - headers = (ngx_array_t *) ((char *) &r->headers_in + offset); - - if (headers->elts == NULL) { - if (ngx_array_init(headers, r->pool, 1, sizeof(ngx_table_elt_t *)) - != NGX_OK) - { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - } - - ph = ngx_array_push(headers); - if (ph == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } - - *ph = h; - return NGX_OK; -} - - ngx_int_t ngx_http_process_request_header(ngx_http_request_t *r) { @@ -2121,7 +2164,7 @@ ngx_http_process_request(ngx_http_request_t *r) } -static ngx_int_t +ngx_int_t ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) { u_char *h, ch; @@ -2213,7 +2256,7 @@ ngx_http_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) } -static ngx_int_t +ngx_int_t ngx_http_set_virtual_server(ngx_http_request_t *r, ngx_str_t *host) { ngx_int_t rc; @@ -2674,6 +2717,8 @@ ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http terminate request count:%d", mr->count); + mr->terminated = 1; + if (rc > 0 && (mr->headers_out.status == 0 || mr->connection->sent == 0)) { mr->headers_out.status = rc; } @@ -2696,8 +2741,11 @@ ngx_http_terminate_request(ngx_http_request_t *r, ngx_int_t rc) if (mr->write_event_handler) { if (mr->blocked) { + r = r->connection->data; + r->connection->error = 1; r->write_event_handler = ngx_http_request_finalizer; + return; } @@ -2736,6 +2784,13 @@ ngx_http_finalize_connection(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_V3) + if (r->connection->quic) { + ngx_http_close_request(r, 0); + return; + } +#endif + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->main->count != 1) { @@ -2779,7 +2834,8 @@ ngx_http_finalize_connection(ngx_http_request_t *r) || (clcf->lingering_close == NGX_HTTP_LINGERING_ON && (r->lingering_close || r->header_in->pos < r->header_in->last - || r->connection->read->ready))) + || r->connection->read->ready + || r->connection->pipeline))) { ngx_http_set_lingering_close(r->connection); return; @@ -2950,6 +3006,20 @@ ngx_http_test_reading(ngx_http_request_t *r) #endif +#if (NGX_HTTP_V3) + + if (c->quic) { + if (rev->error) { + c->error = 1; + err = 0; + goto closed; + } + + return; + } + +#endif + #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { @@ -3149,6 +3219,7 @@ ngx_http_set_keepalive(ngx_http_request_t *r) c->sent = 0; c->destroyed = 0; + c->pipeline = 1; if (rev->timer_set) { ngx_del_timer(rev); @@ -3614,7 +3685,7 @@ ngx_http_post_action(ngx_http_request_t *r) } -static void +void ngx_http_close_request(ngx_http_request_t *r, ngx_int_t rc) { ngx_connection_t *c; @@ -3701,7 +3772,12 @@ ngx_http_free_request(ngx_http_request_t *r, ngx_int_t rc) log->action = "closing request"; - if (r->connection->timedout) { + if (r->connection->timedout +#if (NGX_HTTP_V3) + && r->connection->quic == NULL +#endif + ) + { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (clcf->reset_timedout_connection) { @@ -3774,6 +3850,12 @@ ngx_http_close_connection(ngx_connection_t *c) #endif +#if (NGX_HTTP_V3) + if (c->quic) { + ngx_http_v3_reset_stream(c); + } +#endif + #if (NGX_STAT_STUB) (void) ngx_atomic_fetch_add(ngx_stat_active, -1); #endif diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h index b1269d2..65c8333 100644 --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -24,6 +24,7 @@ #define NGX_HTTP_VERSION_10 1000 #define NGX_HTTP_VERSION_11 1001 #define NGX_HTTP_VERSION_20 2000 +#define NGX_HTTP_VERSION_30 3000 #define NGX_HTTP_UNKNOWN 0x00000001 #define NGX_HTTP_GET 0x00000002 @@ -212,7 +213,7 @@ typedef struct { ngx_table_elt_t *keep_alive; #if (NGX_HTTP_X_FORWARDED_FOR) - ngx_array_t x_forwarded_for; + ngx_table_elt_t *x_forwarded_for; #endif #if (NGX_HTTP_REALIP) @@ -231,17 +232,19 @@ typedef struct { ngx_table_elt_t *date; #endif + ngx_table_elt_t *cookie; + ngx_str_t user; ngx_str_t passwd; - ngx_array_t cookies; - ngx_str_t server; off_t content_length_n; time_t keep_alive_n; unsigned connection_type:2; unsigned chunked:1; + unsigned multi:1; + unsigned multi_linked:1; unsigned msie:1; unsigned msie6:1; unsigned opera:1; @@ -272,6 +275,9 @@ typedef struct { ngx_table_elt_t *expires; ngx_table_elt_t *etag; + ngx_table_elt_t *cache_control; + ngx_table_elt_t *link; + ngx_str_t *override_charset; size_t content_type_len; @@ -280,9 +286,6 @@ typedef struct { u_char *content_type_lowcase; ngx_uint_t content_type_hash; - ngx_array_t cache_control; - ngx_array_t link; - off_t content_length_n; off_t content_offset; time_t date_time; @@ -449,6 +452,7 @@ struct ngx_http_request_s { ngx_http_connection_t *http_connection; ngx_http_v2_stream_t *stream; + ngx_http_v3_parse_t *v3_parse; ngx_http_log_handler_pt log_handler; @@ -541,10 +545,12 @@ struct ngx_http_request_s { unsigned request_complete:1; unsigned request_output:1; unsigned header_sent:1; + unsigned response_sent:1; unsigned expect_tested:1; unsigned root_tested:1; unsigned done:1; unsigned logged:1; + unsigned terminated:1; unsigned buffered:4; @@ -592,8 +598,6 @@ struct ngx_http_request_s { u_char *schema_end; u_char *host_start; u_char *host_end; - u_char *port_start; - u_char *port_end; unsigned http_minor:16; unsigned http_major:16; diff --git a/src/http/ngx_http_request_body.c b/src/http/ngx_http_request_body.c index ad3549f..afb0423 100644 --- a/src/http/ngx_http_request_body.c +++ b/src/http/ngx_http_request_body.c @@ -92,6 +92,13 @@ ngx_http_read_client_request_body(ngx_http_request_t *r, } #endif +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + rc = ngx_http_v3_read_request_body(r); + goto done; + } +#endif + preread = r->header_in->last - r->header_in->pos; if (preread) { @@ -238,6 +245,18 @@ ngx_http_read_unbuffered_request_body(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + rc = ngx_http_v3_read_unbuffered_request_body(r); + + if (rc == NGX_OK) { + r->reading_body = 0; + } + + return rc; + } +#endif + if (r->connection->read->timedout) { r->connection->timedout = 1; return NGX_HTTP_REQUEST_TIME_OUT; @@ -625,6 +644,12 @@ ngx_http_discard_request_body(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_V3) + if (r->http_version == NGX_HTTP_VERSION_30) { + return NGX_OK; + } +#endif + if (ngx_http_test_expect(r) != NGX_OK) { return NGX_HTTP_INTERNAL_SERVER_ERROR; } @@ -920,6 +945,9 @@ ngx_http_test_expect(ngx_http_request_t *r) || r->http_version < NGX_HTTP_VERSION_11 #if (NGX_HTTP_V2) || r->stream != NULL +#endif +#if (NGX_HTTP_V3) + || r->connection->quic != NULL #endif ) { diff --git a/src/http/ngx_http_script.c b/src/http/ngx_http_script.c index bebdbd9..a2b9f1b 100644 --- a/src/http/ngx_http_script.c +++ b/src/http/ngx_http_script.c @@ -1243,6 +1243,7 @@ ngx_http_script_regex_end_code(ngx_http_script_engine_t *e) } r->headers_out.location->hash = 1; + r->headers_out.location->next = NULL; ngx_str_set(&r->headers_out.location->key, "Location"); r->headers_out.location->value = e->buf; diff --git a/src/http/ngx_http_special_response.c b/src/http/ngx_http_special_response.c index 72f56fd..eaf42e3 100644 --- a/src/http/ngx_http_special_response.c +++ b/src/http/ngx_http_special_response.c @@ -649,6 +649,7 @@ ngx_http_send_error_page(ngx_http_request_t *r, ngx_http_err_page_t *err_page) } location->hash = 1; + location->next = NULL; ngx_str_set(&location->key, "Location"); location->value = uri; diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index ded833c..2ce9f21 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -101,6 +101,9 @@ static void ngx_http_upstream_finalize_request(ngx_http_request_t *r, static ngx_int_t ngx_http_upstream_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); +static ngx_int_t + ngx_http_upstream_process_multi_header_lines(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_process_content_length(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); static ngx_int_t ngx_http_upstream_process_last_modified(ngx_http_request_t *r, @@ -147,11 +150,6 @@ static ngx_int_t ngx_http_upstream_rewrite_set_cookie(ngx_http_request_t *r, static ngx_int_t ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset); -#if (NGX_HTTP_GZIP) -static ngx_int_t ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r, - ngx_table_elt_t *h, ngx_uint_t offset); -#endif - static ngx_int_t ngx_http_upstream_add_variables(ngx_conf_t *cf); static ngx_int_t ngx_http_upstream_addr_variable(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); @@ -231,7 +229,7 @@ static ngx_http_upstream_header_t ngx_http_upstream_headers_in[] = { offsetof(ngx_http_headers_out_t, server), 0 }, { ngx_string("WWW-Authenticate"), - ngx_http_upstream_process_header_line, + ngx_http_upstream_process_multi_header_lines, offsetof(ngx_http_upstream_headers_in_t, www_authenticate), ngx_http_upstream_copy_header_line, 0, 0 }, @@ -241,12 +239,13 @@ static ngx_http_upstream_header_t ngx_http_upstream_headers_in[] = { ngx_http_upstream_rewrite_location, 0, 0 }, { ngx_string("Refresh"), - ngx_http_upstream_ignore_header_line, 0, + ngx_http_upstream_process_header_line, + offsetof(ngx_http_upstream_headers_in_t, refresh), ngx_http_upstream_rewrite_refresh, 0, 0 }, { ngx_string("Set-Cookie"), ngx_http_upstream_process_set_cookie, - offsetof(ngx_http_upstream_headers_in_t, cookies), + offsetof(ngx_http_upstream_headers_in_t, set_cookie), ngx_http_upstream_rewrite_set_cookie, 0, 1 }, { ngx_string("Content-Disposition"), @@ -264,8 +263,7 @@ static ngx_http_upstream_header_t ngx_http_upstream_headers_in[] = { offsetof(ngx_http_headers_out_t, expires), 1 }, { ngx_string("Accept-Ranges"), - ngx_http_upstream_process_header_line, - offsetof(ngx_http_upstream_headers_in_t, accept_ranges), + ngx_http_upstream_ignore_header_line, 0, ngx_http_upstream_copy_allow_ranges, offsetof(ngx_http_headers_out_t, accept_ranges), 1 }, @@ -316,12 +314,10 @@ static ngx_http_upstream_header_t ngx_http_upstream_headers_in[] = { ngx_http_upstream_process_transfer_encoding, 0, ngx_http_upstream_ignore_header_line, 0, 0 }, -#if (NGX_HTTP_GZIP) { ngx_string("Content-Encoding"), - ngx_http_upstream_process_header_line, - offsetof(ngx_http_upstream_headers_in_t, content_encoding), - ngx_http_upstream_copy_content_encoding, 0, 0 }, -#endif + ngx_http_upstream_ignore_header_line, 0, + ngx_http_upstream_copy_header_line, + offsetof(ngx_http_headers_out_t, content_encoding), 0 }, { ngx_null_string, NULL, 0, NULL, 0, 0 } }; @@ -525,6 +521,13 @@ ngx_http_upstream_init(ngx_http_request_t *r) } #endif +#if (NGX_HTTP_V3) + if (c->quic) { + ngx_http_upstream_init_request(r); + return; + } +#endif + if (c->read->timer_set) { ngx_del_timer(c->read); } @@ -1358,6 +1361,19 @@ ngx_http_upstream_check_broken_connection(ngx_http_request_t *r, } #endif +#if (NGX_HTTP_V3) + + if (c->quic) { + if (c->write->error) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_CLIENT_CLOSED_REQUEST); + } + + return; + } + +#endif + #if (NGX_HAVE_KQUEUE) if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { @@ -1694,8 +1710,10 @@ ngx_http_upstream_ssl_init_connection(ngx_http_request_t *r, } } - if (u->conf->ssl_certificate && (u->conf->ssl_certificate->lengths - || u->conf->ssl_certificate_key->lengths)) + if (u->conf->ssl_certificate + && u->conf->ssl_certificate->value.len + && (u->conf->ssl_certificate->lengths + || u->conf->ssl_certificate_key->lengths)) { if (ngx_http_upstream_ssl_certificate(r, u, c) != NGX_OK) { ngx_http_upstream_finalize_request(r, u, @@ -2651,7 +2669,7 @@ ngx_http_upstream_intercept_errors(ngx_http_request_t *r, { ngx_int_t status; ngx_uint_t i; - ngx_table_elt_t *h; + ngx_table_elt_t *h, *ho, **ph; ngx_http_err_page_t *err_page; ngx_http_core_loc_conf_t *clcf; @@ -2680,23 +2698,36 @@ ngx_http_upstream_intercept_errors(ngx_http_request_t *r, if (status == NGX_HTTP_UNAUTHORIZED && u->headers_in.www_authenticate) { - h = ngx_list_push(&r->headers_out.headers); + h = u->headers_in.www_authenticate; + ph = &r->headers_out.www_authenticate; - if (h == NULL) { - ngx_http_upstream_finalize_request(r, u, + while (h) { + ho = ngx_list_push(&r->headers_out.headers); + + if (ho == NULL) { + ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_OK; - } + return NGX_OK; + } + + *ho = *h; + ho->next = NULL; - *h = *u->headers_in.www_authenticate; + *ph = ho; + ph = &ho->next; - r->headers_out.www_authenticate = h; + h = h->next; + } } #if (NGX_HTTP_CACHE) if (r->cache) { + if (u->headers_in.no_cache || u->headers_in.expired) { + u->cacheable = 0; + } + if (u->cacheable) { time_t valid; @@ -2791,6 +2822,10 @@ ngx_http_upstream_process_headers(ngx_http_request_t *r, ngx_http_upstream_t *u) umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); + if (u->headers_in.no_cache || u->headers_in.expired) { + u->cacheable = 0; + } + if (u->headers_in.x_accel_redirect && !(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT)) { @@ -2811,6 +2846,10 @@ ngx_http_upstream_process_headers(ngx_http_request_t *r, ngx_http_upstream_t *u) i = 0; } + if (h[i].hash == 0) { + continue; + } + hh = ngx_hash_find(&umcf->headers_in_hash, h[i].hash, h[i].lowcase_key, h[i].key.len); @@ -2864,6 +2903,10 @@ ngx_http_upstream_process_headers(ngx_http_request_t *r, ngx_http_upstream_t *u) i = 0; } + if (h[i].hash == 0) { + continue; + } + if (ngx_hash_find(&u->conf->hide_headers_hash, h[i].hash, h[i].lowcase_key, h[i].key.len)) { @@ -3906,6 +3949,8 @@ ngx_http_upstream_thread_handler(ngx_thread_task_t *task, ngx_file_t *file) r->aio = 1; p->aio = 1; + ngx_add_timer(&task->event, 60000); + return NGX_OK; } @@ -3924,6 +3969,17 @@ ngx_http_upstream_thread_event_handler(ngx_event_t *ev) ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http upstream thread: \"%V?%V\"", &r->uri, &r->args); + if (ev->timedout) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "thread operation took too long"); + ev->timedout = 0; + return; + } + + if (ev->timer_set) { + ngx_del_timer(ev); + } + r->main->blocked--; r->aio = 0; @@ -3941,11 +3997,11 @@ ngx_http_upstream_thread_event_handler(ngx_event_t *ev) #endif - if (r->done) { + if (r->done || r->main->terminated) { /* * trigger connection event handler if the subrequest was - * already finalized; this can happen if the handler is used - * for sendfile() in threads + * already finalized (this can happen if the handler is used + * for sendfile() in threads), or if the request was terminated */ c->write->handler(c->write); @@ -4518,6 +4574,10 @@ ngx_http_upstream_finalize_request(ngx_http_request_t *r, u->peer.connection = NULL; + if (u->pipe) { + u->pipe->upstream = NULL; + } + if (u->pipe && u->pipe->temp_file) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http upstream temp fd: %d", @@ -4615,10 +4675,36 @@ ngx_http_upstream_process_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, ph = (ngx_table_elt_t **) ((char *) &r->upstream->headers_in + offset); - if (*ph == NULL) { - *ph = h; + if (*ph) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\", ignored", + &h->key, &h->value, + &(*ph)->key, &(*ph)->value); + h->hash = 0; + return NGX_OK; } + *ph = h; + h->next = NULL; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_upstream_process_multi_header_lines(ngx_http_request_t *r, + ngx_table_elt_t *h, ngx_uint_t offset) +{ + ngx_table_elt_t **ph; + + ph = (ngx_table_elt_t **) ((char *) &r->upstream->headers_in + offset); + + while (*ph) { ph = &(*ph)->next; } + + *ph = h; + h->next = NULL; + return NGX_OK; } @@ -4639,9 +4725,34 @@ ngx_http_upstream_process_content_length(ngx_http_request_t *r, u = r->upstream; + if (u->headers_in.content_length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\"", + &h->key, &h->value, + &u->headers_in.content_length->key, + &u->headers_in.content_length->value); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (u->headers_in.transfer_encoding) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent \"Content-Length\" and " + "\"Transfer-Encoding\" headers at the same time"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + h->next = NULL; u->headers_in.content_length = h; u->headers_in.content_length_n = ngx_atoof(h->value.data, h->value.len); + if (u->headers_in.content_length_n == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid \"Content-Length\" header: " + "\"%V: %V\"", &h->key, &h->value); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + return NGX_OK; } @@ -4654,6 +4765,18 @@ ngx_http_upstream_process_last_modified(ngx_http_request_t *r, u = r->upstream; + if (u->headers_in.last_modified) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\", ignored", + &h->key, &h->value, + &u->headers_in.last_modified->key, + &u->headers_in.last_modified->value); + h->hash = 0; + return NGX_OK; + } + + h->next = NULL; u->headers_in.last_modified = h; u->headers_in.last_modified_time = ngx_parse_http_time(h->value.data, h->value.len); @@ -4666,26 +4789,16 @@ static ngx_int_t ngx_http_upstream_process_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - ngx_array_t *pa; ngx_table_elt_t **ph; ngx_http_upstream_t *u; u = r->upstream; - pa = &u->headers_in.cookies; - - if (pa->elts == NULL) { - if (ngx_array_init(pa, r->pool, 1, sizeof(ngx_table_elt_t *)) != NGX_OK) - { - return NGX_ERROR; - } - } + ph = &u->headers_in.set_cookie; - ph = ngx_array_push(pa); - if (ph == NULL) { - return NGX_ERROR; - } + while (*ph) { ph = &(*ph)->next; } *ph = h; + h->next = NULL; #if (NGX_HTTP_CACHE) if (!(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_SET_COOKIE)) { @@ -4701,26 +4814,16 @@ static ngx_int_t ngx_http_upstream_process_cache_control(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - ngx_array_t *pa; - ngx_table_elt_t **ph; - ngx_http_upstream_t *u; + ngx_table_elt_t **ph; + ngx_http_upstream_t *u; u = r->upstream; - pa = &u->headers_in.cache_control; + ph = &u->headers_in.cache_control; - if (pa->elts == NULL) { - if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK) - { - return NGX_ERROR; - } - } - - ph = ngx_array_push(pa); - if (ph == NULL) { - return NGX_ERROR; - } + while (*ph) { ph = &(*ph)->next; } *ph = h; + h->next = NULL; #if (NGX_HTTP_CACHE) { @@ -4735,18 +4838,18 @@ ngx_http_upstream_process_cache_control(ngx_http_request_t *r, return NGX_OK; } - if (r->cache->valid_sec != 0 && u->headers_in.x_accel_expires != NULL) { - return NGX_OK; - } - start = h->value.data; last = start + h->value.len; + if (r->cache->valid_sec != 0 && u->headers_in.x_accel_expires != NULL) { + goto extensions; + } + if (ngx_strlcasestrn(start, last, (u_char *) "no-cache", 8 - 1) != NULL || ngx_strlcasestrn(start, last, (u_char *) "no-store", 8 - 1) != NULL || ngx_strlcasestrn(start, last, (u_char *) "private", 7 - 1) != NULL) { - u->cacheable = 0; + u->headers_in.no_cache = 1; return NGX_OK; } @@ -4776,13 +4879,16 @@ ngx_http_upstream_process_cache_control(ngx_http_request_t *r, } if (n == 0) { - u->cacheable = 0; + u->headers_in.no_cache = 1; return NGX_OK; } r->cache->valid_sec = ngx_time() + n; + u->headers_in.expired = 0; } +extensions: + p = ngx_strlcasestrn(start, last, (u_char *) "stale-while-revalidate=", 23 - 1); @@ -4842,7 +4948,20 @@ ngx_http_upstream_process_expires(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_http_upstream_t *u; u = r->upstream; + + if (u->headers_in.expires) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\", ignored", + &h->key, &h->value, + &u->headers_in.expires->key, + &u->headers_in.expires->value); + h->hash = 0; + return NGX_OK; + } + u->headers_in.expires = h; + h->next = NULL; #if (NGX_HTTP_CACHE) { @@ -4863,7 +4982,7 @@ ngx_http_upstream_process_expires(ngx_http_request_t *r, ngx_table_elt_t *h, expires = ngx_parse_http_time(h->value.data, h->value.len); if (expires == NGX_ERROR || expires < ngx_time()) { - u->cacheable = 0; + u->headers_in.expired = 1; return NGX_OK; } @@ -4882,7 +5001,20 @@ ngx_http_upstream_process_accel_expires(ngx_http_request_t *r, ngx_http_upstream_t *u; u = r->upstream; + + if (u->headers_in.x_accel_expires) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\", ignored", + &h->key, &h->value, + &u->headers_in.x_accel_expires->key, + &u->headers_in.x_accel_expires->value); + h->hash = 0; + return NGX_OK; + } + u->headers_in.x_accel_expires = h; + h->next = NULL; #if (NGX_HTTP_CACHE) { @@ -4914,6 +5046,8 @@ ngx_http_upstream_process_accel_expires(ngx_http_request_t *r, default: r->cache->valid_sec = ngx_time() + n; + u->headers_in.no_cache = 0; + u->headers_in.expired = 0; return NGX_OK; } } @@ -4925,6 +5059,8 @@ ngx_http_upstream_process_accel_expires(ngx_http_request_t *r, if (n != NGX_ERROR) { r->cache->valid_sec = n; + u->headers_in.no_cache = 0; + u->headers_in.expired = 0; } } #endif @@ -4941,7 +5077,20 @@ ngx_http_upstream_process_limit_rate(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_http_upstream_t *u; u = r->upstream; + + if (u->headers_in.x_accel_limit_rate) { + ngx_log_error(NGX_LOG_WARN, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\", ignored", + &h->key, &h->value, + &u->headers_in.x_accel_limit_rate->key, + &u->headers_in.x_accel_limit_rate->value); + h->hash = 0; + return NGX_OK; + } + u->headers_in.x_accel_limit_rate = h; + h->next = NULL; if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_LIMIT_RATE) { return NGX_OK; @@ -5000,7 +5149,11 @@ static ngx_int_t ngx_http_upstream_process_charset(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - if (r->upstream->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_CHARSET) { + ngx_http_upstream_t *u; + + u = r->upstream; + + if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_XA_CHARSET) { return NGX_OK; } @@ -5014,13 +5167,22 @@ static ngx_int_t ngx_http_upstream_process_connection(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - r->upstream->headers_in.connection = h; + ngx_table_elt_t **ph; + ngx_http_upstream_t *u; + + u = r->upstream; + ph = &u->headers_in.connection; + + while (*ph) { ph = &(*ph)->next; } + + *ph = h; + h->next = NULL; if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len, (u_char *) "close", 5 - 1) != NULL) { - r->upstream->headers_in.connection_close = 1; + u->headers_in.connection_close = 1; } return NGX_OK; @@ -5031,13 +5193,40 @@ static ngx_int_t ngx_http_upstream_process_transfer_encoding(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - r->upstream->headers_in.transfer_encoding = h; + ngx_http_upstream_t *u; - if (ngx_strlcasestrn(h->value.data, h->value.data + h->value.len, - (u_char *) "chunked", 7 - 1) - != NULL) + u = r->upstream; + + if (u->headers_in.transfer_encoding) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent duplicate header line: \"%V: %V\", " + "previous value: \"%V: %V\"", + &h->key, &h->value, + &u->headers_in.transfer_encoding->key, + &u->headers_in.transfer_encoding->value); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (u->headers_in.content_length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent \"Content-Length\" and " + "\"Transfer-Encoding\" headers at the same time"); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + u->headers_in.transfer_encoding = h; + h->next = NULL; + + if (h->value.len == 7 + && ngx_strncasecmp(h->value.data, (u_char *) "chunked", 7) == 0) { - r->upstream->headers_in.chunked = 1; + u->headers_in.chunked = 1; + + } else { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent unknown \"Transfer-Encoding\": \"%V\"", + &h->value); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; } return NGX_OK; @@ -5048,29 +5237,74 @@ static ngx_int_t ngx_http_upstream_process_vary(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - ngx_http_upstream_t *u; + ngx_table_elt_t **ph; + ngx_http_upstream_t *u; u = r->upstream; - u->headers_in.vary = h; + ph = &u->headers_in.vary; + + while (*ph) { ph = &(*ph)->next; } + + *ph = h; + h->next = NULL; #if (NGX_HTTP_CACHE) + { + u_char *p; + size_t len; + ngx_str_t vary; if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_VARY) { return NGX_OK; } - if (r->cache == NULL) { + if (r->cache == NULL || !u->cacheable) { return NGX_OK; } - if (h->value.len > NGX_HTTP_CACHE_VARY_LEN - || (h->value.len == 1 && h->value.data[0] == '*')) - { + if (h->value.len == 1 && h->value.data[0] == '*') { u->cacheable = 0; + return NGX_OK; } - r->cache->vary = h->value; + if (u->headers_in.vary->next) { + + len = 0; + + for (h = u->headers_in.vary; h; h = h->next) { + len += h->value.len + 2; + } + + len -= 2; + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + vary.len = len; + vary.data = p; + + for (h = u->headers_in.vary; h; h = h->next) { + p = ngx_copy(p, h->value.data, h->value.len); + + if (h->next == NULL) { + break; + } + + *p++ = ','; *p++ = ' '; + } + + } else { + vary = h->value; + } + + if (vary.len > NGX_HTTP_CACHE_VARY_LEN) { + u->cacheable = 0; + } + + r->cache->vary = vary; + } #endif return NGX_OK; @@ -5093,6 +5327,7 @@ ngx_http_upstream_copy_header_line(ngx_http_request_t *r, ngx_table_elt_t *h, if (offset) { ph = (ngx_table_elt_t **) ((char *) &r->headers_out + offset); *ph = ho; + ho->next = NULL; } return NGX_OK; @@ -5103,18 +5338,8 @@ static ngx_int_t ngx_http_upstream_copy_multi_header_lines(ngx_http_request_t *r, ngx_table_elt_t *h, ngx_uint_t offset) { - ngx_array_t *pa; ngx_table_elt_t *ho, **ph; - pa = (ngx_array_t *) ((char *) &r->headers_out + offset); - - if (pa->elts == NULL) { - if (ngx_array_init(pa, r->pool, 2, sizeof(ngx_table_elt_t *)) != NGX_OK) - { - return NGX_ERROR; - } - } - ho = ngx_list_push(&r->headers_out.headers); if (ho == NULL) { return NGX_ERROR; @@ -5122,12 +5347,12 @@ ngx_http_upstream_copy_multi_header_lines(ngx_http_request_t *r, *ho = *h; - ph = ngx_array_push(pa); - if (ph == NULL) { - return NGX_ERROR; - } + ph = (ngx_table_elt_t **) ((char *) &r->headers_out + offset); + + while (*ph) { ph = &(*ph)->next; } *ph = ho; + ho->next = NULL; return NGX_OK; } @@ -5197,6 +5422,7 @@ ngx_http_upstream_copy_last_modified(ngx_http_request_t *r, ngx_table_elt_t *h, } *ho = *h; + ho->next = NULL; r->headers_out.last_modified = ho; r->headers_out.last_modified_time = @@ -5219,6 +5445,7 @@ ngx_http_upstream_rewrite_location(ngx_http_request_t *r, ngx_table_elt_t *h, } *ho = *h; + ho->next = NULL; if (r->upstream->rewrite_redirect) { rc = r->upstream->rewrite_redirect(r, ho, 0); @@ -5264,6 +5491,7 @@ ngx_http_upstream_rewrite_refresh(ngx_http_request_t *r, ngx_table_elt_t *h, } *ho = *h; + ho->next = NULL; if (r->upstream->rewrite_redirect) { @@ -5309,6 +5537,7 @@ ngx_http_upstream_rewrite_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h, } *ho = *h; + ho->next = NULL; if (r->upstream->rewrite_cookie) { rc = r->upstream->rewrite_cookie(r, ho); @@ -5362,6 +5591,7 @@ ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r, } *ho = *h; + ho->next = NULL; r->headers_out.accept_ranges = ho; @@ -5369,29 +5599,6 @@ ngx_http_upstream_copy_allow_ranges(ngx_http_request_t *r, } -#if (NGX_HTTP_GZIP) - -static ngx_int_t -ngx_http_upstream_copy_content_encoding(ngx_http_request_t *r, - ngx_table_elt_t *h, ngx_uint_t offset) -{ - ngx_table_elt_t *ho; - - ho = ngx_list_push(&r->headers_out.headers); - if (ho == NULL) { - return NGX_ERROR; - } - - *ho = *h; - - r->headers_out.content_encoding = ho; - - return NGX_OK; -} - -#endif - - static ngx_int_t ngx_http_upstream_add_variables(ngx_conf_t *cf) { @@ -5703,7 +5910,7 @@ ngx_http_upstream_header_variable(ngx_http_request_t *r, return NGX_OK; } - return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, + return ngx_http_variable_unknown_header(r, v, (ngx_str_t *) data, &r->upstream->headers_in.headers.part, sizeof("upstream_http_") - 1); } @@ -5718,7 +5925,7 @@ ngx_http_upstream_trailer_variable(ngx_http_request_t *r, return NGX_OK; } - return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, + return ngx_http_variable_unknown_header(r, v, (ngx_str_t *) data, &r->upstream->headers_in.trailers.part, sizeof("upstream_trailer_") - 1); } @@ -5740,9 +5947,9 @@ ngx_http_upstream_cookie_variable(ngx_http_request_t *r, s.len = name->len - (sizeof("upstream_cookie_") - 1); s.data = name->data + sizeof("upstream_cookie_") - 1; - if (ngx_http_parse_set_cookie_lines(&r->upstream->headers_in.cookies, + if (ngx_http_parse_set_cookie_lines(r, r->upstream->headers_in.set_cookie, &s, &cookie) - == NGX_DECLINED) + == NULL) { v->not_found = 1; return NGX_OK; diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index 3db7b06..9a17a03 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -280,23 +280,21 @@ typedef struct { ngx_table_elt_t *last_modified; ngx_table_elt_t *location; - ngx_table_elt_t *accept_ranges; + ngx_table_elt_t *refresh; ngx_table_elt_t *www_authenticate; ngx_table_elt_t *transfer_encoding; ngx_table_elt_t *vary; -#if (NGX_HTTP_GZIP) - ngx_table_elt_t *content_encoding; -#endif - - ngx_array_t cache_control; - ngx_array_t cookies; + ngx_table_elt_t *cache_control; + ngx_table_elt_t *set_cookie; off_t content_length_n; time_t last_modified_time; unsigned connection_close:1; unsigned chunked:1; + unsigned no_cache:1; + unsigned expired:1; } ngx_http_upstream_headers_in_t; diff --git a/src/http/ngx_http_variables.c b/src/http/ngx_http_variables.c index 942dacd..4f0bd0e 100644 --- a/src/http/ngx_http_variables.c +++ b/src/http/ngx_http_variables.c @@ -27,8 +27,6 @@ static ngx_int_t ngx_http_variable_header(ngx_http_request_t *r, static ngx_int_t ngx_http_variable_cookies(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); -static ngx_int_t ngx_http_variable_headers(ngx_http_request_t *r, - ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_headers_internal(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data, u_char sep); @@ -63,6 +61,8 @@ static ngx_int_t ngx_http_variable_proxy_protocol_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_proxy_protocol_port(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_variable_proxy_protocol_tlv(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_server_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_http_variable_server_port(ngx_http_request_t *r, @@ -178,12 +178,12 @@ static ngx_http_variable_t ngx_http_core_variables[] = { #endif #if (NGX_HTTP_X_FORWARDED_FOR) - { ngx_string("http_x_forwarded_for"), NULL, ngx_http_variable_headers, + { ngx_string("http_x_forwarded_for"), NULL, ngx_http_variable_header, offsetof(ngx_http_request_t, headers_in.x_forwarded_for), 0, 0 }, #endif { ngx_string("http_cookie"), NULL, ngx_http_variable_cookies, - offsetof(ngx_http_request_t, headers_in.cookies), 0, 0 }, + offsetof(ngx_http_request_t, headers_in.cookie), 0, 0 }, { ngx_string("content_length"), NULL, ngx_http_variable_content_length, 0, 0, 0 }, @@ -216,6 +216,10 @@ static ngx_http_variable_t ngx_http_core_variables[] = { ngx_http_variable_proxy_protocol_port, offsetof(ngx_proxy_protocol_t, dst_port), 0, 0 }, + { ngx_string("proxy_protocol_tlv_"), NULL, + ngx_http_variable_proxy_protocol_tlv, + 0, NGX_HTTP_VAR_PREFIX, 0 }, + { ngx_string("server_addr"), NULL, ngx_http_variable_server_addr, 0, 0, 0 }, { ngx_string("server_port"), NULL, ngx_http_variable_server_port, 0, 0, 0 }, @@ -327,10 +331,10 @@ static ngx_http_variable_t ngx_http_core_variables[] = { { ngx_string("sent_http_transfer_encoding"), NULL, ngx_http_variable_sent_transfer_encoding, 0, 0, 0 }, - { ngx_string("sent_http_cache_control"), NULL, ngx_http_variable_headers, + { ngx_string("sent_http_cache_control"), NULL, ngx_http_variable_header, offsetof(ngx_http_request_t, headers_out.cache_control), 0, 0 }, - { ngx_string("sent_http_link"), NULL, ngx_http_variable_headers, + { ngx_string("sent_http_link"), NULL, ngx_http_variable_header, offsetof(ngx_http_request_t, headers_out.link), 0, 0 }, { ngx_string("limit_rate"), ngx_http_variable_set_limit_rate, @@ -807,22 +811,7 @@ static ngx_int_t ngx_http_variable_header(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - ngx_table_elt_t *h; - - h = *(ngx_table_elt_t **) ((char *) r + data); - - if (h) { - v->len = h->value.len; - v->valid = 1; - v->no_cacheable = 0; - v->not_found = 0; - v->data = h->value.data; - - } else { - v->not_found = 1; - } - - return NGX_OK; + return ngx_http_variable_headers_internal(r, v, data, ','); } @@ -834,38 +823,25 @@ ngx_http_variable_cookies(ngx_http_request_t *r, } -static ngx_int_t -ngx_http_variable_headers(ngx_http_request_t *r, - ngx_http_variable_value_t *v, uintptr_t data) -{ - return ngx_http_variable_headers_internal(r, v, data, ','); -} - - static ngx_int_t ngx_http_variable_headers_internal(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data, u_char sep) { - size_t len; - u_char *p, *end; - ngx_uint_t i, n; - ngx_array_t *a; - ngx_table_elt_t **h; - - a = (ngx_array_t *) ((char *) r + data); + size_t len; + u_char *p, *end; + ngx_table_elt_t *h, *th; - n = a->nelts; - h = a->elts; + h = *(ngx_table_elt_t **) ((char *) r + data); len = 0; - for (i = 0; i < n; i++) { + for (th = h; th; th = th->next) { - if (h[i]->hash == 0) { + if (th->hash == 0) { continue; } - len += h[i]->value.len + 2; + len += th->value.len + 2; } if (len == 0) { @@ -879,9 +855,9 @@ ngx_http_variable_headers_internal(ngx_http_request_t *r, v->no_cacheable = 0; v->not_found = 0; - if (n == 1) { - v->len = (*h)->value.len; - v->data = (*h)->value.data; + if (h->next == NULL) { + v->len = h->value.len; + v->data = h->value.data; return NGX_OK; } @@ -896,13 +872,13 @@ ngx_http_variable_headers_internal(ngx_http_request_t *r, end = p + len; - for (i = 0; /* void */ ; i++) { + for (th = h; th; th = th->next) { - if (h[i]->hash == 0) { + if (th->hash == 0) { continue; } - p = ngx_copy(p, h[i]->value.data, h[i]->value.len); + p = ngx_copy(p, th->value.data, th->value.len); if (p == end) { break; @@ -919,7 +895,7 @@ static ngx_int_t ngx_http_variable_unknown_header_in(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, + return ngx_http_variable_unknown_header(r, v, (ngx_str_t *) data, &r->headers_in.headers.part, sizeof("http_") - 1); } @@ -929,7 +905,7 @@ static ngx_int_t ngx_http_variable_unknown_header_out(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, + return ngx_http_variable_unknown_header(r, v, (ngx_str_t *) data, &r->headers_out.headers.part, sizeof("sent_http_") - 1); } @@ -939,19 +915,26 @@ static ngx_int_t ngx_http_variable_unknown_trailer_out(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) { - return ngx_http_variable_unknown_header(v, (ngx_str_t *) data, + return ngx_http_variable_unknown_header(r, v, (ngx_str_t *) data, &r->headers_out.trailers.part, sizeof("sent_trailer_") - 1); } ngx_int_t -ngx_http_variable_unknown_header(ngx_http_variable_value_t *v, ngx_str_t *var, +ngx_http_variable_unknown_header(ngx_http_request_t *r, + ngx_http_variable_value_t *v, ngx_str_t *var, ngx_list_part_t *part, size_t prefix) { - u_char ch; + u_char *p, ch; + size_t len; ngx_uint_t i, n; - ngx_table_elt_t *header; + ngx_table_elt_t *header, *h, **ph; + + ph = &h; +#if (NGX_SUPPRESS_WARN) + len = 0; +#endif header = part->elts; @@ -971,7 +954,11 @@ ngx_http_variable_unknown_header(ngx_http_variable_value_t *v, ngx_str_t *var, continue; } - for (n = 0; n + prefix < var->len && n < header[i].key.len; n++) { + if (header[i].key.len != var->len - prefix) { + continue; + } + + for (n = 0; n < var->len - prefix; n++) { ch = header[i].key.data[n]; if (ch >= 'A' && ch <= 'Z') { @@ -986,18 +973,59 @@ ngx_http_variable_unknown_header(ngx_http_variable_value_t *v, ngx_str_t *var, } } - if (n + prefix == var->len && n == header[i].key.len) { - v->len = header[i].value.len; - v->valid = 1; - v->no_cacheable = 0; - v->not_found = 0; - v->data = header[i].value.data; - - return NGX_OK; + if (n != var->len - prefix) { + continue; } + + len += header[i].value.len + 2; + + *ph = &header[i]; + ph = &header[i].next; } - v->not_found = 1; + *ph = NULL; + + if (h == NULL) { + v->not_found = 1; + return NGX_OK; + } + + len -= 2; + + if (h->next == NULL) { + + v->len = h->value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = h->value.data; + + return NGX_OK; + } + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + return NGX_ERROR; + } + + v->len = len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + for ( ;; ) { + + p = ngx_copy(p, h->value.data, h->value.len); + + if (h->next == NULL) { + break; + } + + *p++ = ','; *p++ = ' '; + + h = h->next; + } return NGX_OK; } @@ -1050,8 +1078,8 @@ ngx_http_variable_cookie(ngx_http_request_t *r, ngx_http_variable_value_t *v, s.len = name->len - (sizeof("cookie_") - 1); s.data = name->data + sizeof("cookie_") - 1; - if (ngx_http_parse_multi_header_lines(&r->headers_in.cookies, &s, &cookie) - == NGX_DECLINED) + if (ngx_http_parse_multi_header_lines(r, r->headers_in.cookie, &s, &cookie) + == NULL) { v->not_found = 1; return NGX_OK; @@ -1366,6 +1394,39 @@ ngx_http_variable_proxy_protocol_port(ngx_http_request_t *r, } +static ngx_int_t +ngx_http_variable_proxy_protocol_tlv(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_str_t *name = (ngx_str_t *) data; + + ngx_int_t rc; + ngx_str_t tlv, value; + + tlv.len = name->len - (sizeof("proxy_protocol_tlv_") - 1); + tlv.data = name->data + sizeof("proxy_protocol_tlv_") - 1; + + rc = ngx_proxy_protocol_get_tlv(r->connection, &tlv, &value); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + v->not_found = 1; + return NGX_OK; + } + + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = value.data; + + return NGX_OK; +} + + static ngx_int_t ngx_http_variable_server_addr(ngx_http_request_t *r, ngx_http_variable_value_t *v, uintptr_t data) @@ -1879,7 +1940,7 @@ ngx_http_variable_sent_location(ngx_http_request_t *r, ngx_str_set(&name, "sent_http_location"); - return ngx_http_variable_unknown_header(v, &name, + return ngx_http_variable_unknown_header(r, v, &name, &r->headers_out.headers.part, sizeof("sent_http_") - 1); } diff --git a/src/http/ngx_http_variables.h b/src/http/ngx_http_variables.h index f3f7f3c..6a661a2 100644 --- a/src/http/ngx_http_variables.h +++ b/src/http/ngx_http_variables.h @@ -57,8 +57,9 @@ ngx_http_variable_value_t *ngx_http_get_flushed_variable(ngx_http_request_t *r, ngx_http_variable_value_t *ngx_http_get_variable(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t key); -ngx_int_t ngx_http_variable_unknown_header(ngx_http_variable_value_t *v, - ngx_str_t *var, ngx_list_part_t *part, size_t prefix); +ngx_int_t ngx_http_variable_unknown_header(ngx_http_request_t *r, + ngx_http_variable_value_t *v, ngx_str_t *var, ngx_list_part_t *part, + size_t prefix); #if (NGX_PCRE) diff --git a/src/http/ngx_http_write_filter_module.c b/src/http/ngx_http_write_filter_module.c index 932f26d..9188ee9 100644 --- a/src/http/ngx_http_write_filter_module.c +++ b/src/http/ngx_http_write_filter_module.c @@ -227,7 +227,8 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) if (size == 0 && !(c->buffered & NGX_LOWLEVEL_BUFFERED) - && !(last && c->need_last_buf)) + && !(last && c->need_last_buf) + && !(flush && c->need_flush_buf)) { if (last || flush || sync) { for (cl = r->out; cl; /* void */) { @@ -239,6 +240,10 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) r->out = NULL; c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + if (last) { + r->response_sent = 1; + } + return NGX_OK; } @@ -345,6 +350,10 @@ ngx_http_write_filter(ngx_http_request_t *r, ngx_chain_t *in) c->buffered &= ~NGX_HTTP_WRITE_BUFFERED; + if (last) { + r->response_sent = 1; + } + if ((c->buffered & NGX_LOWLEVEL_BUFFERED) && r->postponed == NULL) { return NGX_AGAIN; } diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 0e45a7b..0f5bd3d 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -11,14 +11,6 @@ #include <ngx_http_v2_module.h> -typedef struct { - ngx_str_t name; - ngx_uint_t offset; - ngx_uint_t hash; - ngx_http_header_t *hh; -} ngx_http_v2_parse_header_t; - - /* errors */ #define NGX_HTTP_V2_NO_ERROR 0x0 #define NGX_HTTP_V2_PROTOCOL_ERROR 0x1 @@ -63,8 +55,6 @@ static void ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c); static void ngx_http_v2_lingering_close(ngx_connection_t *c); static void ngx_http_v2_lingering_close_handler(ngx_event_t *rev); -static u_char *ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, - u_char *pos, u_char *end); static u_char *ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, @@ -128,7 +118,7 @@ static ngx_int_t ngx_http_v2_parse_int(ngx_http_v2_connection_t *h2c, u_char **pos, u_char *end, ngx_uint_t prefix); static ngx_http_v2_stream_t *ngx_http_v2_create_stream( - ngx_http_v2_connection_t *h2c, ngx_uint_t push); + ngx_http_v2_connection_t *h2c); static ngx_http_v2_node_t *ngx_http_v2_get_node_by_id( ngx_http_v2_connection_t *h2c, ngx_uint_t sid, ngx_uint_t alloc); static ngx_http_v2_node_t *ngx_http_v2_get_closed_node( @@ -164,14 +154,11 @@ static ngx_int_t ngx_http_v2_parse_scheme(ngx_http_request_t *r, ngx_str_t *value); static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value); -static ngx_int_t ngx_http_v2_parse_header(ngx_http_request_t *r, - ngx_http_v2_parse_header_t *header, ngx_str_t *value); static ngx_int_t ngx_http_v2_construct_request_line(ngx_http_request_t *r); static ngx_int_t ngx_http_v2_cookie(ngx_http_request_t *r, ngx_http_v2_header_t *header); static ngx_int_t ngx_http_v2_construct_cookie_header(ngx_http_request_t *r); static void ngx_http_v2_run_request(ngx_http_request_t *r); -static void ngx_http_v2_run_request_handler(ngx_event_t *ev); static ngx_int_t ngx_http_v2_process_request_body(ngx_http_request_t *r, u_char *pos, size_t size, ngx_uint_t last, ngx_uint_t flush); static ngx_int_t ngx_http_v2_filter_request_body(ngx_http_request_t *r); @@ -212,26 +199,10 @@ static ngx_http_v2_handler_pt ngx_http_v2_frame_states[] = { (sizeof(ngx_http_v2_frame_states) / sizeof(ngx_http_v2_handler_pt)) -static ngx_http_v2_parse_header_t ngx_http_v2_parse_headers[] = { - { ngx_string("host"), - offsetof(ngx_http_headers_in_t, host), 0, NULL }, - - { ngx_string("accept-encoding"), - offsetof(ngx_http_headers_in_t, accept_encoding), 0, NULL }, - - { ngx_string("accept-language"), - offsetof(ngx_http_headers_in_t, accept_language), 0, NULL }, - - { ngx_string("user-agent"), - offsetof(ngx_http_headers_in_t, user_agent), 0, NULL }, - - { ngx_null_string, 0, 0, NULL } -}; - - void ngx_http_v2_init(ngx_event_t *rev) { + u_char *p, *end; ngx_connection_t *c; ngx_pool_cleanup_t *cln; ngx_http_connection_t *hc; @@ -276,7 +247,6 @@ ngx_http_v2_init(ngx_event_t *rev) h2scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v2_module); - h2c->concurrent_pushes = h2scf->concurrent_pushes; h2c->priority_limit = ngx_max(h2scf->concurrent_streams, 100); h2c->pool = ngx_create_pool(h2scf->pool_size, h2c->connection->log); @@ -314,8 +284,7 @@ ngx_http_v2_init(ngx_event_t *rev) return; } - h2c->state.handler = hc->proxy_protocol ? ngx_http_v2_state_proxy_protocol - : ngx_http_v2_state_preface; + h2c->state.handler = ngx_http_v2_state_preface; ngx_queue_init(&h2c->waiting); ngx_queue_init(&h2c->dependencies); @@ -335,6 +304,23 @@ ngx_http_v2_init(ngx_event_t *rev) c->idle = 1; ngx_reusable_connection(c, 0); + if (c->buffer) { + p = c->buffer->pos; + end = c->buffer->last; + + do { + p = h2c->state.handler(h2c, p, end); + + if (p == NULL) { + return; + } + + } while (p != end); + + h2c->total_bytes += p - c->buffer->pos; + c->buffer->pos = p; + } + ngx_http_v2_read_handler(rev); } @@ -361,6 +347,7 @@ ngx_http_v2_read_handler(ngx_event_t *rev) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http2 read handler"); h2c->blocked = 1; + h2c->new_streams = 0; if (c->close) { c->close = 0; @@ -370,7 +357,7 @@ ngx_http_v2_read_handler(ngx_event_t *rev) return; } - if (!h2c->processing && !h2c->pushing) { + if (!h2c->processing) { ngx_http_v2_finalize_connection(h2c, NGX_HTTP_V2_NO_ERROR); return; } @@ -399,13 +386,11 @@ ngx_http_v2_read_handler(ngx_event_t *rev) h2mcf = ngx_http_get_module_main_conf(h2c->http_connection->conf_ctx, ngx_http_v2_module); - available = h2mcf->recv_buffer_size - 2 * NGX_HTTP_V2_STATE_BUFFER_SIZE; + available = h2mcf->recv_buffer_size - NGX_HTTP_V2_STATE_BUFFER_SIZE; do { p = h2mcf->recv_buffer; - - ngx_memcpy(p, h2c->state.buffer, NGX_HTTP_V2_STATE_BUFFER_SIZE); - end = p + h2c->state.buffer_used; + end = ngx_cpymem(p, h2c->state.buffer, h2c->state.buffer_used); n = c->recv(c, end, available); @@ -413,9 +398,7 @@ ngx_http_v2_read_handler(ngx_event_t *rev) break; } - if (n == 0 - && (h2c->state.incomplete || h2c->processing || h2c->pushing)) - { + if (n == 0 && (h2c->state.incomplete || h2c->processing)) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client prematurely closed connection"); } @@ -638,7 +621,7 @@ ngx_http_v2_handle_connection(ngx_http_v2_connection_t *h2c) ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; - if (h2c->last_out || h2c->processing || h2c->pushing) { + if (h2c->last_out || h2c->processing) { return; } @@ -846,32 +829,11 @@ ngx_http_v2_lingering_close_handler(ngx_event_t *rev) } -static u_char * -ngx_http_v2_state_proxy_protocol(ngx_http_v2_connection_t *h2c, u_char *pos, - u_char *end) -{ - ngx_log_t *log; - - log = h2c->connection->log; - log->action = "reading PROXY protocol"; - - pos = ngx_proxy_protocol_read(h2c->connection, pos, end); - - log->action = "processing HTTP/2 connection"; - - if (pos == NULL) { - return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_PROTOCOL_ERROR); - } - - return ngx_http_v2_state_preface(h2c, pos, end); -} - - static u_char * ngx_http_v2_state_preface(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { - static const u_char preface[] = "PRI * HTTP/2.0\r\n"; + static const u_char preface[] = NGX_HTTP_V2_PREFACE_START; if ((size_t) (end - pos) < sizeof(preface) - 1) { return ngx_http_v2_state_save(h2c, pos, end, ngx_http_v2_state_preface); @@ -892,7 +854,7 @@ static u_char * ngx_http_v2_state_preface_end(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end) { - static const u_char preface[] = "\r\nSM\r\n\r\n"; + static const u_char preface[] = NGX_HTTP_V2_PREFACE_END; if ((size_t) (end - pos) < sizeof(preface) - 1) { return ngx_http_v2_state_save(h2c, pos, end, @@ -1321,6 +1283,14 @@ ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, goto rst_stream; } + if (h2c->new_streams++ >= 2 * h2scf->concurrent_streams) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent too many streams at once"); + + status = NGX_HTTP_V2_REFUSED_STREAM; + goto rst_stream; + } + if (!h2c->settings_ack && !(h2c->state.flags & NGX_HTTP_V2_END_STREAM_FLAG) && h2scf->preread_size < NGX_HTTP_V2_DEFAULT_WINDOW) @@ -1344,7 +1314,7 @@ ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, h2c->closed_nodes--; } - stream = ngx_http_v2_create_stream(h2c, 0); + stream = ngx_http_v2_create_stream(h2c); if (stream == NULL) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } @@ -1386,6 +1356,12 @@ ngx_http_v2_state_headers(ngx_http_v2_connection_t *h2c, u_char *pos, rst_stream: + if (h2c->refused_streams++ > ngx_max(h2scf->concurrent_streams, 100)) { + ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, + "client sent too many refused streams"); + return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_NO_ERROR); + } + if (ngx_http_v2_send_rst_stream(h2c, h2c->state.sid, status) != NGX_OK) { return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } @@ -1730,6 +1706,7 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, size_t len; ngx_int_t rc; ngx_table_elt_t *h; + ngx_connection_t *fc; ngx_http_header_t *hh; ngx_http_request_t *r; ngx_http_v2_header_t *header; @@ -1789,17 +1766,11 @@ ngx_http_v2_state_process_header(ngx_http_v2_connection_t *h2c, u_char *pos, } r = h2c->state.stream->request; + fc = r->connection; /* TODO Optimization: validate headers while parsing. */ if (ngx_http_v2_validate_header(r, header) != NGX_OK) { - if (ngx_http_v2_terminate_stream(h2c, h2c->state.stream, - NGX_HTTP_V2_PROTOCOL_ERROR) - == NGX_ERROR) - { - return ngx_http_v2_connection_error(h2c, - NGX_HTTP_V2_INTERNAL_ERROR); - } - + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); goto error; } @@ -1886,6 +1857,8 @@ error: h2c->state.stream = NULL; + ngx_http_run_posted_requests(fc); + return ngx_http_v2_state_header_complete(h2c, pos, end); } @@ -2136,11 +2109,6 @@ ngx_http_v2_state_rst_stream(ngx_http_v2_connection_t *h2c, u_char *pos, "client canceled stream %ui", h2c->state.sid); break; - case NGX_HTTP_V2_REFUSED_STREAM: - ngx_log_error(NGX_LOG_INFO, fc->log, 0, - "client refused stream %ui", h2c->state.sid); - break; - case NGX_HTTP_V2_INTERNAL_ERROR: ngx_log_error(NGX_LOG_INFO, fc->log, 0, "client terminated stream %ui due to internal error", @@ -2208,7 +2176,6 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, { ssize_t window_delta; ngx_uint_t id, value; - ngx_http_v2_srv_conf_t *h2scf; ngx_http_v2_out_frame_t *frame; window_delta = 0; @@ -2270,14 +2237,6 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, NGX_HTTP_V2_PROTOCOL_ERROR); } - h2c->push_disabled = !value; - break; - - case NGX_HTTP_V2_MAX_STREAMS_SETTING: - h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, - ngx_http_v2_module); - - h2c->concurrent_pushes = ngx_min(value, h2scf->concurrent_pushes); break; case NGX_HTTP_V2_HEADER_TABLE_SIZE_SETTING: @@ -2631,7 +2590,7 @@ ngx_http_v2_state_save(ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end, return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_INTERNAL_ERROR); } - ngx_memcpy(h2c->state.buffer, pos, NGX_HTTP_V2_STATE_BUFFER_SIZE); + ngx_memcpy(h2c->state.buffer, pos, size); h2c->state.buffer_used = size; h2c->state.handler = handler; @@ -2732,163 +2691,6 @@ ngx_http_v2_parse_int(ngx_http_v2_connection_t *h2c, u_char **pos, u_char *end, } -ngx_http_v2_stream_t * -ngx_http_v2_push_stream(ngx_http_v2_stream_t *parent, ngx_str_t *path) -{ - ngx_int_t rc; - ngx_str_t value; - ngx_pool_t *pool; - ngx_uint_t index; - ngx_table_elt_t **h; - ngx_connection_t *fc; - ngx_http_request_t *r; - ngx_http_v2_node_t *node; - ngx_http_v2_stream_t *stream; - ngx_http_v2_srv_conf_t *h2scf; - ngx_http_v2_connection_t *h2c; - ngx_http_v2_parse_header_t *header; - - h2c = parent->connection; - - pool = ngx_create_pool(1024, h2c->connection->log); - if (pool == NULL) { - goto rst_stream; - } - - node = ngx_http_v2_get_node_by_id(h2c, h2c->last_push, 1); - - if (node == NULL) { - ngx_destroy_pool(pool); - goto rst_stream; - } - - stream = ngx_http_v2_create_stream(h2c, 1); - if (stream == NULL) { - - if (node->parent == NULL) { - h2scf = ngx_http_get_module_srv_conf(h2c->http_connection->conf_ctx, - ngx_http_v2_module); - - index = ngx_http_v2_index(h2scf, h2c->last_push); - h2c->streams_index[index] = node->index; - - ngx_queue_insert_tail(&h2c->closed, &node->reuse); - h2c->closed_nodes++; - } - - ngx_destroy_pool(pool); - goto rst_stream; - } - - if (node->parent) { - ngx_queue_remove(&node->reuse); - h2c->closed_nodes--; - } - - stream->pool = pool; - - r = stream->request; - fc = r->connection; - - stream->in_closed = 1; - stream->node = node; - - node->stream = stream; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2 push stream sid:%ui " - "depends on %ui excl:0 weight:16", - h2c->last_push, parent->node->id); - - node->weight = NGX_HTTP_V2_DEFAULT_WEIGHT; - ngx_http_v2_set_dependency(h2c, node, parent->node->id, 0); - - r->method_name = ngx_http_core_get_method; - r->method = NGX_HTTP_GET; - - r->schema.data = ngx_pstrdup(pool, &parent->request->schema); - if (r->schema.data == NULL) { - goto close; - } - - r->schema.len = parent->request->schema.len; - - value.data = ngx_pstrdup(pool, path); - if (value.data == NULL) { - goto close; - } - - value.len = path->len; - - rc = ngx_http_v2_parse_path(r, &value); - - if (rc != NGX_OK) { - goto error; - } - - for (header = ngx_http_v2_parse_headers; header->name.len; header++) { - h = (ngx_table_elt_t **) - ((char *) &parent->request->headers_in + header->offset); - - if (*h == NULL) { - continue; - } - - value.len = (*h)->value.len; - - value.data = ngx_pnalloc(pool, value.len + 1); - if (value.data == NULL) { - goto close; - } - - ngx_memcpy(value.data, (*h)->value.data, value.len); - value.data[value.len] = '\0'; - - rc = ngx_http_v2_parse_header(r, header, &value); - - if (rc != NGX_OK) { - goto error; - } - } - - fc->write->handler = ngx_http_v2_run_request_handler; - ngx_post_event(fc->write, &ngx_posted_events); - - return stream; - -error: - - if (rc == NGX_ABORT) { - /* header handler has already finalized request */ - ngx_http_run_posted_requests(fc); - return NULL; - } - - if (rc == NGX_DECLINED) { - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - ngx_http_run_posted_requests(fc); - return NULL; - } - -close: - - ngx_http_v2_close_stream(stream, NGX_HTTP_INTERNAL_SERVER_ERROR); - - return NULL; - -rst_stream: - - if (ngx_http_v2_send_rst_stream(h2c, h2c->last_push, - NGX_HTTP_INTERNAL_SERVER_ERROR) - != NGX_OK) - { - h2c->connection->error = 1; - } - - return NULL; -} - - static ngx_int_t ngx_http_v2_send_settings(ngx_http_v2_connection_t *h2c) { @@ -3160,7 +2962,7 @@ ngx_http_v2_frame_handler(ngx_http_v2_connection_t *h2c, static ngx_http_v2_stream_t * -ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t push) +ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c) { ngx_log_t *log; ngx_event_t *rev, *wev; @@ -3215,13 +3017,7 @@ ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t push) ngx_memcpy(log, h2c->connection->log, sizeof(ngx_log_t)); log->data = ctx; - - if (push) { - log->action = "processing pushed request headers"; - - } else { - log->action = "reading client request headers"; - } + log->action = "reading client request headers"; ngx_memzero(rev, sizeof(ngx_event_t)); @@ -3293,12 +3089,7 @@ ngx_http_v2_create_stream(ngx_http_v2_connection_t *h2c, ngx_uint_t push) stream->send_window = h2c->init_window; stream->recv_window = h2scf->preread_size; - if (push) { - h2c->pushing++; - - } else { - h2c->processing++; - } + h2c->processing++; h2c->priority_limit += h2scf->concurrent_streams; @@ -3720,46 +3511,42 @@ ngx_http_v2_parse_scheme(ngx_http_request_t *r, ngx_str_t *value) static ngx_int_t ngx_http_v2_parse_authority(ngx_http_request_t *r, ngx_str_t *value) -{ - return ngx_http_v2_parse_header(r, &ngx_http_v2_parse_headers[0], value); -} - - -static ngx_int_t -ngx_http_v2_parse_header(ngx_http_request_t *r, - ngx_http_v2_parse_header_t *header, ngx_str_t *value) { ngx_table_elt_t *h; + ngx_http_header_t *hh; ngx_http_core_main_conf_t *cmcf; + static ngx_str_t host = ngx_string("host"); + h = ngx_list_push(&r->headers_in.headers); if (h == NULL) { return NGX_ERROR; } - h->key.len = header->name.len; - h->key.data = header->name.data; - h->lowcase_key = header->name.data; + h->hash = ngx_hash(ngx_hash(ngx_hash('h', 'o'), 's'), 't'); - if (header->hh == NULL) { - header->hash = ngx_hash_key(header->name.data, header->name.len); + h->key.len = host.len; + h->key.data = host.data; - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + h->value.len = value->len; + h->value.data = value->data; - header->hh = ngx_hash_find(&cmcf->headers_in_hash, header->hash, - h->lowcase_key, h->key.len); - if (header->hh == NULL) { - return NGX_ERROR; - } - } + h->lowcase_key = host.data; - h->hash = header->hash; + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - h->value.len = value->len; - h->value.data = value->data; + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); - if (header->hh->handler(r, h, header->hh->offset) != NGX_OK) { - /* header handler has already finalized request */ + if (hh == NULL) { + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_host() + */ return NGX_ABORT; } @@ -3946,10 +3733,22 @@ static void ngx_http_v2_run_request(ngx_http_request_t *r) { ngx_connection_t *fc; + ngx_http_v2_srv_conf_t *h2scf; ngx_http_v2_connection_t *h2c; fc = r->connection; + h2scf = ngx_http_get_module_srv_conf(r, ngx_http_v2_module); + + if (!h2scf->enable && !r->http_connection->addr_conf->http2) { + ngx_log_error(NGX_LOG_INFO, fc->log, 0, + "client attempted to request the server name " + "for which the negotiated protocol is disabled"); + + ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); + goto failed; + } + if (ngx_http_v2_construct_request_line(r) != NGX_OK) { goto failed; } @@ -3990,22 +3789,6 @@ failed: } -static void -ngx_http_v2_run_request_handler(ngx_event_t *ev) -{ - ngx_connection_t *fc; - ngx_http_request_t *r; - - fc = ev->data; - r = fc->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 run request handler"); - - ngx_http_v2_run_request(r); -} - - ngx_int_t ngx_http_v2_read_request_body(ngx_http_request_t *r) { @@ -4609,7 +4392,6 @@ void ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc) { ngx_pool_t *pool; - ngx_uint_t push; ngx_event_t *ev; ngx_connection_t *fc; ngx_http_v2_node_t *node; @@ -4618,10 +4400,9 @@ ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc) h2c = stream->connection; node = stream->node; - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2 close stream %ui, queued %ui, " - "processing %ui, pushing %ui", - node->id, stream->queued, h2c->processing, h2c->pushing); + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 close stream %ui, queued %ui, processing %ui", + node->id, stream->queued, h2c->processing); fc = stream->request->connection; @@ -4656,8 +4437,6 @@ ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc) h2c->state.stream = NULL; } - push = stream->node->id % 2 == 0; - node->stream = NULL; ngx_queue_insert_tail(&h2c->closed, &node->reuse); @@ -4707,14 +4486,9 @@ ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc) fc->data = h2c->free_fake_connections; h2c->free_fake_connections = fc; - if (push) { - h2c->pushing--; - - } else { - h2c->processing--; - } + h2c->processing--; - if (h2c->processing || h2c->pushing || h2c->blocked) { + if (h2c->processing || h2c->blocked) { return; } @@ -4890,7 +4664,7 @@ ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c, } } - if (!h2c->processing && !h2c->pushing) { + if (!h2c->processing) { goto done; } @@ -4938,7 +4712,7 @@ ngx_http_v2_finalize_connection(ngx_http_v2_connection_t *h2c, h2c->blocked = 0; - if (h2c->processing || h2c->pushing) { + if (h2c->processing) { c->error = 1; return; } diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h index 70ee287..6751b30 100644 --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -24,8 +24,6 @@ #define NGX_HTTP_V2_MAX_FIELD \ (127 + (1 << (NGX_HTTP_V2_INT_OCTETS - 1) * 7) - 1) -#define NGX_HTTP_V2_STREAM_ID_SIZE 4 - #define NGX_HTTP_V2_FRAME_HEADER_SIZE 9 /* frame types */ @@ -63,6 +61,15 @@ typedef u_char *(*ngx_http_v2_handler_pt) (ngx_http_v2_connection_t *h2c, u_char *pos, u_char *end); +typedef struct { + ngx_flag_t enable; + size_t pool_size; + ngx_uint_t concurrent_streams; + size_t preread_size; + ngx_uint_t streams_index_mask; +} ngx_http_v2_srv_conf_t; + + typedef struct { ngx_str_t name; ngx_str_t value; @@ -124,11 +131,10 @@ struct ngx_http_v2_connection_s { ngx_uint_t processing; ngx_uint_t frames; ngx_uint_t idle; + ngx_uint_t new_streams; + ngx_uint_t refused_streams; ngx_uint_t priority_limit; - ngx_uint_t pushing; - ngx_uint_t concurrent_pushes; - size_t send_window; size_t recv_window; size_t init_window; @@ -153,17 +159,15 @@ struct ngx_http_v2_connection_s { ngx_queue_t dependencies; ngx_queue_t closed; + ngx_uint_t closed_nodes; ngx_uint_t last_sid; - ngx_uint_t last_push; time_t lingering_time; - unsigned closed_nodes:8; unsigned settings_ack:1; unsigned table_update:1; unsigned blocked:1; unsigned goaway:1; - unsigned push_disabled:1; }; @@ -293,9 +297,6 @@ void ngx_http_v2_init(ngx_event_t *rev); ngx_int_t ngx_http_v2_read_request_body(ngx_http_request_t *r); ngx_int_t ngx_http_v2_read_unbuffered_request_body(ngx_http_request_t *r); -ngx_http_v2_stream_t *ngx_http_v2_push_stream(ngx_http_v2_stream_t *parent, - ngx_str_t *path); - void ngx_http_v2_close_stream(ngx_http_v2_stream_t *stream, ngx_int_t rc); ngx_int_t ngx_http_v2_send_output_queue(ngx_http_v2_connection_t *h2c); @@ -397,20 +398,25 @@ ngx_int_t ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size); #define NGX_HTTP_V2_STATUS_404_INDEX 13 #define NGX_HTTP_V2_STATUS_500_INDEX 14 -#define NGX_HTTP_V2_ACCEPT_ENCODING_INDEX 16 -#define NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX 17 #define NGX_HTTP_V2_CONTENT_LENGTH_INDEX 28 #define NGX_HTTP_V2_CONTENT_TYPE_INDEX 31 #define NGX_HTTP_V2_DATE_INDEX 33 #define NGX_HTTP_V2_LAST_MODIFIED_INDEX 44 #define NGX_HTTP_V2_LOCATION_INDEX 46 #define NGX_HTTP_V2_SERVER_INDEX 54 -#define NGX_HTTP_V2_USER_AGENT_INDEX 58 #define NGX_HTTP_V2_VARY_INDEX 59 +#define NGX_HTTP_V2_PREFACE_START "PRI * HTTP/2.0\r\n" +#define NGX_HTTP_V2_PREFACE_END "\r\nSM\r\n\r\n" +#define NGX_HTTP_V2_PREFACE NGX_HTTP_V2_PREFACE_START \ + NGX_HTTP_V2_PREFACE_END + u_char *ngx_http_v2_string_encode(u_char *dst, u_char *src, size_t len, u_char *tmp, ngx_uint_t lower); +extern ngx_module_t ngx_http_v2_module; + + #endif /* _NGX_HTTP_V2_H_INCLUDED_ */ diff --git a/src/http/v2/ngx_http_v2_filter_module.c b/src/http/v2/ngx_http_v2_filter_module.c index 9ffb155..1e2cafa 100644 --- a/src/http/v2/ngx_http_v2_filter_module.c +++ b/src/http/v2/ngx_http_v2_filter_module.c @@ -27,39 +27,8 @@ #define NGX_HTTP_V2_NO_TRAILERS (ngx_http_v2_out_frame_t *) -1 -typedef struct { - ngx_str_t name; - u_char index; - ngx_uint_t offset; -} ngx_http_v2_push_header_t; - - -static ngx_http_v2_push_header_t ngx_http_v2_push_headers[] = { - { ngx_string(":authority"), NGX_HTTP_V2_AUTHORITY_INDEX, - offsetof(ngx_http_headers_in_t, host) }, - - { ngx_string("accept-encoding"), NGX_HTTP_V2_ACCEPT_ENCODING_INDEX, - offsetof(ngx_http_headers_in_t, accept_encoding) }, - - { ngx_string("accept-language"), NGX_HTTP_V2_ACCEPT_LANGUAGE_INDEX, - offsetof(ngx_http_headers_in_t, accept_language) }, - - { ngx_string("user-agent"), NGX_HTTP_V2_USER_AGENT_INDEX, - offsetof(ngx_http_headers_in_t, user_agent) }, -}; - -#define NGX_HTTP_V2_PUSH_HEADERS \ - (sizeof(ngx_http_v2_push_headers) / sizeof(ngx_http_v2_push_header_t)) - - -static ngx_int_t ngx_http_v2_push_resources(ngx_http_request_t *r); -static ngx_int_t ngx_http_v2_push_resource(ngx_http_request_t *r, - ngx_str_t *path, ngx_str_t *binary); - static ngx_http_v2_out_frame_t *ngx_http_v2_create_headers_frame( ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin); -static ngx_http_v2_out_frame_t *ngx_http_v2_create_push_frame( - ngx_http_request_t *r, u_char *pos, u_char *end); static ngx_http_v2_out_frame_t *ngx_http_v2_create_trailers_frame( ngx_http_request_t *r); @@ -82,8 +51,6 @@ static ngx_inline ngx_int_t ngx_http_v2_filter_send( static ngx_int_t ngx_http_v2_headers_frame_handler( ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); -static ngx_int_t ngx_http_v2_push_frame_handler( - ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); static ngx_int_t ngx_http_v2_data_frame_handler( ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame); static ngx_inline void ngx_http_v2_handle_frame( @@ -244,15 +211,6 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) h2c = stream->connection; - if (!h2c->push_disabled && !h2c->goaway - && stream->node->id % 2 == 1 - && r->method != NGX_HTTP_HEAD) - { - if (ngx_http_v2_push_resources(r) != NGX_OK) { - return NGX_ERROR; - } - } - len = h2c->table_update ? 1 : 0; len += status ? 1 : 1 + ngx_http_v2_literal_size("418"); @@ -653,7 +611,7 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) ngx_http_v2_queue_blocked_frame(h2c, frame); - stream->queued++; + stream->queued = 1; cln = ngx_http_cleanup_add(r, 0); if (cln == NULL) { @@ -665,416 +623,12 @@ ngx_http_v2_header_filter(ngx_http_request_t *r) fc->send_chain = ngx_http_v2_send_chain; fc->need_last_buf = 1; + fc->need_flush_buf = 1; return ngx_http_v2_filter_send(fc, stream); } -static ngx_int_t -ngx_http_v2_push_resources(ngx_http_request_t *r) -{ - u_char *start, *end, *last; - ngx_int_t rc; - ngx_str_t path; - ngx_uint_t i, push; - ngx_table_elt_t **h; - ngx_http_v2_loc_conf_t *h2lcf; - ngx_http_complex_value_t *pushes; - ngx_str_t binary[NGX_HTTP_V2_PUSH_HEADERS]; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http2 push resources"); - - ngx_memzero(binary, NGX_HTTP_V2_PUSH_HEADERS * sizeof(ngx_str_t)); - - h2lcf = ngx_http_get_module_loc_conf(r, ngx_http_v2_module); - - if (h2lcf->pushes) { - pushes = h2lcf->pushes->elts; - - for (i = 0; i < h2lcf->pushes->nelts; i++) { - - if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { - return NGX_ERROR; - } - - if (path.len == 0) { - continue; - } - - if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { - continue; - } - - rc = ngx_http_v2_push_resource(r, &path, binary); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_ABORT) { - return NGX_OK; - } - - /* NGX_OK, NGX_DECLINED */ - } - } - - if (!h2lcf->push_preload) { - return NGX_OK; - } - - h = r->headers_out.link.elts; - - for (i = 0; i < r->headers_out.link.nelts; i++) { - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http2 parse link: \"%V\"", &h[i]->value); - - start = h[i]->value.data; - end = h[i]->value.data + h[i]->value.len; - - next_link: - - while (start < end && *start == ' ') { start++; } - - if (start == end || *start++ != '<') { - continue; - } - - while (start < end && *start == ' ') { start++; } - - for (last = start; last < end && *last != '>'; last++) { - /* void */ - } - - if (last == start || last == end) { - continue; - } - - path.len = last - start; - path.data = start; - - start = last + 1; - - while (start < end && *start == ' ') { start++; } - - if (start == end) { - continue; - } - - if (*start == ',') { - start++; - goto next_link; - } - - if (*start++ != ';') { - continue; - } - - last = ngx_strlchr(start, end, ','); - - if (last == NULL) { - last = end; - } - - push = 0; - - for ( ;; ) { - - while (start < last && *start == ' ') { start++; } - - if (last - start >= 6 - && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) - { - start += 6; - - if (start == last || *start == ' ' || *start == ';') { - push = 0; - break; - } - - goto next_param; - } - - if (last - start >= 11 - && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) - { - start += 11; - - if (start == last || *start == ' ' || *start == ';') { - push = 1; - } - - goto next_param; - } - - if (last - start >= 4 - && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) - { - start += 4; - - while (start < last && *start == ' ') { start++; } - - if (start == last || *start++ != '"') { - goto next_param; - } - - for ( ;; ) { - - while (start < last && *start == ' ') { start++; } - - if (last - start >= 7 - && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) - { - start += 7; - - if (start < last && (*start == ' ' || *start == '"')) { - push = 1; - break; - } - } - - while (start < last && *start != ' ' && *start != '"') { - start++; - } - - if (start == last) { - break; - } - - if (*start == '"') { - break; - } - - start++; - } - } - - next_param: - - start = ngx_strlchr(start, last, ';'); - - if (start == NULL) { - break; - } - - start++; - } - - if (push) { - while (path.len && path.data[path.len - 1] == ' ') { - path.len--; - } - } - - if (push && path.len - && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) - { - rc = ngx_http_v2_push_resource(r, &path, binary); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_ABORT) { - return NGX_OK; - } - - /* NGX_OK, NGX_DECLINED */ - } - - if (last < end) { - start = last + 1; - goto next_link; - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v2_push_resource(ngx_http_request_t *r, ngx_str_t *path, - ngx_str_t *binary) -{ - u_char *start, *pos, *tmp; - size_t len; - ngx_str_t *value; - ngx_uint_t i; - ngx_table_elt_t **h; - ngx_connection_t *fc; - ngx_http_v2_stream_t *stream; - ngx_http_v2_out_frame_t *frame; - ngx_http_v2_connection_t *h2c; - ngx_http_v2_push_header_t *ph; - - fc = r->connection; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, "http2 push resource"); - - stream = r->stream; - h2c = stream->connection; - - if (!ngx_path_separator(path->data[0])) { - ngx_log_error(NGX_LOG_WARN, fc->log, 0, - "non-absolute path \"%V\" not pushed", path); - return NGX_DECLINED; - } - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2 pushing:%ui limit:%ui", - h2c->pushing, h2c->concurrent_pushes); - - if (h2c->pushing >= h2c->concurrent_pushes) { - return NGX_ABORT; - } - - if (h2c->last_push == 0x7ffffffe) { - return NGX_ABORT; - } - - if (path->len > NGX_HTTP_V2_MAX_FIELD) { - return NGX_DECLINED; - } - - if (r->headers_in.host == NULL) { - return NGX_ABORT; - } - - ph = ngx_http_v2_push_headers; - - len = ngx_max(r->schema.len, path->len); - - if (binary[0].len) { - tmp = ngx_palloc(r->pool, len); - if (tmp == NULL) { - return NGX_ERROR; - } - - } else { - for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { - h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset); - - if (*h) { - len = ngx_max(len, (*h)->value.len); - } - } - - tmp = ngx_palloc(r->pool, len); - if (tmp == NULL) { - return NGX_ERROR; - } - - for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { - h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset); - - if (*h == NULL) { - continue; - } - - value = &(*h)->value; - - len = 1 + NGX_HTTP_V2_INT_OCTETS + value->len; - - pos = ngx_pnalloc(r->pool, len); - if (pos == NULL) { - return NGX_ERROR; - } - - binary[i].data = pos; - - *pos++ = ngx_http_v2_inc_indexed(ph[i].index); - pos = ngx_http_v2_write_value(pos, value->data, value->len, tmp); - - binary[i].len = pos - binary[i].data; - } - } - - len = (h2c->table_update ? 1 : 0) - + 1 - + 1 + NGX_HTTP_V2_INT_OCTETS + path->len - + 1 + NGX_HTTP_V2_INT_OCTETS + r->schema.len; - - for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { - len += binary[i].len; - } - - pos = ngx_pnalloc(r->pool, len); - if (pos == NULL) { - return NGX_ERROR; - } - - start = pos; - - if (h2c->table_update) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 table size update: 0"); - *pos++ = (1 << 5) | 0; - h2c->table_update = 0; - } - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \":method: GET\""); - - *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_METHOD_GET_INDEX); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \":path: %V\"", path); - - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_PATH_INDEX); - pos = ngx_http_v2_write_value(pos, path->data, path->len, tmp); - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \":scheme: %V\"", &r->schema); - - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { - *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTPS_INDEX); - - } else if (r->schema.len == 4 - && ngx_strncmp(r->schema.data, "http", 4) == 0) - { - *pos++ = ngx_http_v2_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX); - - } else { - *pos++ = ngx_http_v2_inc_indexed(NGX_HTTP_V2_SCHEME_HTTP_INDEX); - pos = ngx_http_v2_write_value(pos, r->schema.data, r->schema.len, tmp); - } - - for (i = 0; i < NGX_HTTP_V2_PUSH_HEADERS; i++) { - h = (ngx_table_elt_t **) ((char *) &r->headers_in + ph[i].offset); - - if (*h == NULL) { - continue; - } - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, fc->log, 0, - "http2 push header: \"%V: %V\"", - &ph[i].name, &(*h)->value); - - pos = ngx_cpymem(pos, binary[i].data, binary[i].len); - } - - frame = ngx_http_v2_create_push_frame(r, start, pos); - if (frame == NULL) { - return NGX_ERROR; - } - - ngx_http_v2_queue_blocked_frame(h2c, frame); - - stream->queued++; - - stream = ngx_http_v2_push_stream(stream, path); - - if (stream) { - stream->request->request_length = pos - start; - return NGX_OK; - } - - return NGX_ERROR; -} - - static ngx_http_v2_out_frame_t * ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos, u_char *end, ngx_uint_t fin) @@ -1180,125 +734,6 @@ ngx_http_v2_create_headers_frame(ngx_http_request_t *r, u_char *pos, } -static ngx_http_v2_out_frame_t * -ngx_http_v2_create_push_frame(ngx_http_request_t *r, u_char *pos, u_char *end) -{ - u_char type, flags; - size_t rest, frame_size, len; - ngx_buf_t *b; - ngx_chain_t *cl, **ll; - ngx_http_v2_stream_t *stream; - ngx_http_v2_out_frame_t *frame; - ngx_http_v2_connection_t *h2c; - - stream = r->stream; - h2c = stream->connection; - rest = NGX_HTTP_V2_STREAM_ID_SIZE + (end - pos); - - frame = ngx_palloc(r->pool, sizeof(ngx_http_v2_out_frame_t)); - if (frame == NULL) { - return NULL; - } - - frame->handler = ngx_http_v2_push_frame_handler; - frame->stream = stream; - frame->length = rest; - frame->blocked = 1; - frame->fin = 0; - - ll = &frame->first; - - type = NGX_HTTP_V2_PUSH_PROMISE_FRAME; - flags = NGX_HTTP_V2_NO_FLAG; - frame_size = h2c->frame_size; - - for ( ;; ) { - if (rest <= frame_size) { - frame_size = rest; - flags |= NGX_HTTP_V2_END_HEADERS_FLAG; - } - - b = ngx_create_temp_buf(r->pool, - NGX_HTTP_V2_FRAME_HEADER_SIZE - + ((type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) - ? NGX_HTTP_V2_STREAM_ID_SIZE : 0)); - if (b == NULL) { - return NULL; - } - - b->last = ngx_http_v2_write_len_and_type(b->last, frame_size, type); - *b->last++ = flags; - b->last = ngx_http_v2_write_sid(b->last, stream->node->id); - - b->tag = (ngx_buf_tag_t) &ngx_http_v2_module; - - if (type == NGX_HTTP_V2_PUSH_PROMISE_FRAME) { - h2c->last_push += 2; - - b->last = ngx_http_v2_write_sid(b->last, h2c->last_push); - len = frame_size - NGX_HTTP_V2_STREAM_ID_SIZE; - - } else { - len = frame_size; - } - - cl = ngx_alloc_chain_link(r->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - - *ll = cl; - ll = &cl->next; - - b = ngx_calloc_buf(r->pool); - if (b == NULL) { - return NULL; - } - - b->pos = pos; - - pos += len; - - b->last = pos; - b->start = b->pos; - b->end = b->last; - b->temporary = 1; - - cl = ngx_alloc_chain_link(r->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - - *ll = cl; - ll = &cl->next; - - rest -= frame_size; - - if (rest) { - frame->length += NGX_HTTP_V2_FRAME_HEADER_SIZE; - - type = NGX_HTTP_V2_CONTINUATION_FRAME; - continue; - } - - cl->next = NULL; - frame->last = cl; - - ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http2:%ui create PUSH_PROMISE frame %p: " - "sid:%ui len:%uz", - stream->node->id, frame, h2c->last_push, - frame->length); - - return frame; - } -} - - static ngx_http_v2_out_frame_t * ngx_http_v2_create_trailers_frame(ngx_http_request_t *r) { @@ -1815,7 +1250,11 @@ ngx_http_v2_waiting_queue(ngx_http_v2_connection_t *h2c, static ngx_inline ngx_int_t ngx_http_v2_filter_send(ngx_connection_t *fc, ngx_http_v2_stream_t *stream) { - if (stream->queued == 0) { + ngx_connection_t *c; + + c = stream->connection->connection; + + if (stream->queued == 0 && !c->buffered) { fc->buffered &= ~NGX_HTTP_V2_BUFFERED; return NGX_OK; } @@ -1898,62 +1337,6 @@ ngx_http_v2_headers_frame_handler(ngx_http_v2_connection_t *h2c, } -static ngx_int_t -ngx_http_v2_push_frame_handler(ngx_http_v2_connection_t *h2c, - ngx_http_v2_out_frame_t *frame) -{ - ngx_chain_t *cl, *ln; - ngx_http_v2_stream_t *stream; - - stream = frame->stream; - cl = frame->first; - - for ( ;; ) { - if (cl->buf->pos != cl->buf->last) { - frame->first = cl; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2:%ui PUSH_PROMISE frame %p was sent partially", - stream->node->id, frame); - - return NGX_AGAIN; - } - - ln = cl->next; - - if (cl->buf->tag == (ngx_buf_tag_t) &ngx_http_v2_module) { - cl->next = stream->free_frame_headers; - stream->free_frame_headers = cl; - - } else { - cl->next = stream->free_bufs; - stream->free_bufs = cl; - } - - if (cl == frame->last) { - break; - } - - cl = ln; - } - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "http2:%ui PUSH_PROMISE frame %p was sent", - stream->node->id, frame); - - stream->request->header_size += NGX_HTTP_V2_FRAME_HEADER_SIZE - + frame->length; - - h2c->payload_bytes += frame->length; - - ngx_http_v2_handle_frame(stream, frame); - - ngx_http_v2_handle_stream(h2c, stream); - - return NGX_OK; -} - - static ngx_int_t ngx_http_v2_data_frame_handler(ngx_http_v2_connection_t *h2c, ngx_http_v2_out_frame_t *frame) diff --git a/src/http/v2/ngx_http_v2_module.c b/src/http/v2/ngx_http_v2_module.c index 0050886..d64488c 100644 --- a/src/http/v2/ngx_http_v2_module.c +++ b/src/http/v2/ngx_http_v2_module.c @@ -27,8 +27,6 @@ static void *ngx_http_v2_create_loc_conf(ngx_conf_t *cf); static char *ngx_http_v2_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); -static char *ngx_http_v2_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); - static char *ngx_http_v2_recv_buffer_size(ngx_conf_t *cf, void *post, void *data); static char *ngx_http_v2_pool_size(ngx_conf_t *cf, void *post, void *data); @@ -75,6 +73,13 @@ static ngx_conf_post_t ngx_http_v2_chunk_size_post = static ngx_command_t ngx_http_v2_commands[] = { + { ngx_string("http2"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v2_srv_conf_t, enable), + NULL }, + { ngx_string("http2_recv_buffer_size"), NGX_HTTP_MAIN_CONF|NGX_CONF_TAKE1, ngx_conf_set_size_slot, @@ -98,9 +103,9 @@ static ngx_command_t ngx_http_v2_commands[] = { { ngx_string("http2_max_concurrent_pushes"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, - ngx_conf_set_num_slot, - NGX_HTTP_SRV_CONF_OFFSET, - offsetof(ngx_http_v2_srv_conf_t, concurrent_pushes), + ngx_http_v2_obsolete, + 0, + 0, NULL }, { ngx_string("http2_max_requests"), @@ -161,15 +166,15 @@ static ngx_command_t ngx_http_v2_commands[] = { { ngx_string("http2_push_preload"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, - ngx_conf_set_flag_slot, - NGX_HTTP_LOC_CONF_OFFSET, - offsetof(ngx_http_v2_loc_conf_t, push_preload), + ngx_http_v2_obsolete, + 0, + 0, NULL }, { ngx_string("http2_push"), NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, - ngx_http_v2_push, - NGX_HTTP_LOC_CONF_OFFSET, + ngx_http_v2_obsolete, + 0, 0, NULL }, @@ -314,10 +319,11 @@ ngx_http_v2_create_srv_conf(ngx_conf_t *cf) return NULL; } + h2scf->enable = NGX_CONF_UNSET; + h2scf->pool_size = NGX_CONF_UNSET_SIZE; h2scf->concurrent_streams = NGX_CONF_UNSET_UINT; - h2scf->concurrent_pushes = NGX_CONF_UNSET_UINT; h2scf->preread_size = NGX_CONF_UNSET_SIZE; @@ -333,12 +339,12 @@ ngx_http_v2_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_http_v2_srv_conf_t *prev = parent; ngx_http_v2_srv_conf_t *conf = child; + ngx_conf_merge_value(conf->enable, prev->enable, 0); + ngx_conf_merge_size_value(conf->pool_size, prev->pool_size, 4096); ngx_conf_merge_uint_value(conf->concurrent_streams, prev->concurrent_streams, 128); - ngx_conf_merge_uint_value(conf->concurrent_pushes, - prev->concurrent_pushes, 10); ngx_conf_merge_size_value(conf->preread_size, prev->preread_size, 65536); @@ -359,17 +365,8 @@ ngx_http_v2_create_loc_conf(ngx_conf_t *cf) return NULL; } - /* - * set by ngx_pcalloc(): - * - * h2lcf->pushes = NULL; - */ - h2lcf->chunk_size = NGX_CONF_UNSET_SIZE; - h2lcf->push_preload = NGX_CONF_UNSET; - h2lcf->push = NGX_CONF_UNSET; - return h2lcf; } @@ -382,72 +379,6 @@ ngx_http_v2_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_size_value(conf->chunk_size, prev->chunk_size, 8 * 1024); - ngx_conf_merge_value(conf->push, prev->push, 1); - - if (conf->push && conf->pushes == NULL) { - conf->pushes = prev->pushes; - } - - ngx_conf_merge_value(conf->push_preload, prev->push_preload, 0); - - return NGX_CONF_OK; -} - - -static char * -ngx_http_v2_push(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_http_v2_loc_conf_t *h2lcf = conf; - - ngx_str_t *value; - ngx_http_complex_value_t *cv; - ngx_http_compile_complex_value_t ccv; - - value = cf->args->elts; - - if (ngx_strcmp(value[1].data, "off") == 0) { - - if (h2lcf->pushes) { - return "\"off\" parameter cannot be used with URI"; - } - - if (h2lcf->push == 0) { - return "is duplicate"; - } - - h2lcf->push = 0; - return NGX_CONF_OK; - } - - if (h2lcf->push == 0) { - return "URI cannot be used with \"off\" parameter"; - } - - h2lcf->push = 1; - - if (h2lcf->pushes == NULL) { - h2lcf->pushes = ngx_array_create(cf->pool, 1, - sizeof(ngx_http_complex_value_t)); - if (h2lcf->pushes == NULL) { - return NGX_CONF_ERROR; - } - } - - cv = ngx_array_push(h2lcf->pushes); - if (cv == NULL) { - return NGX_CONF_ERROR; - } - - ngx_memzero(&ccv, sizeof(ngx_http_compile_complex_value_t)); - - ccv.cf = cf; - ccv.value = &value[1]; - ccv.complex_value = cv; - - if (ngx_http_compile_complex_value(&ccv) != NGX_OK) { - return NGX_CONF_ERROR; - } - return NGX_CONF_OK; } @@ -457,7 +388,7 @@ ngx_http_v2_recv_buffer_size(ngx_conf_t *cf, void *post, void *data) { size_t *sp = data; - if (*sp <= 2 * NGX_HTTP_V2_STATE_BUFFER_SIZE) { + if (*sp <= NGX_HTTP_V2_STATE_BUFFER_SIZE) { return "value is too small"; } @@ -551,10 +482,17 @@ ngx_http_v2_obsolete(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_conf_deprecated_t *d = cmd->post; - ngx_conf_log_error(NGX_LOG_WARN, cf, 0, - "the \"%s\" directive is obsolete, " - "use the \"%s\" directive instead", - d->old_name, d->new_name); + if (d) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "the \"%s\" directive is obsolete, " + "use the \"%s\" directive instead", + d->old_name, d->new_name); + + } else { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "the \"%V\" directive is obsolete, ignored", + &cmd->name); + } return NGX_CONF_OK; } diff --git a/src/http/v2/ngx_http_v2_module.h b/src/http/v2/ngx_http_v2_module.h index ca4a0bf..07a595c 100644 --- a/src/http/v2/ngx_http_v2_module.h +++ b/src/http/v2/ngx_http_v2_module.h @@ -20,26 +20,9 @@ typedef struct { } ngx_http_v2_main_conf_t; -typedef struct { - size_t pool_size; - ngx_uint_t concurrent_streams; - ngx_uint_t concurrent_pushes; - size_t preread_size; - ngx_uint_t streams_index_mask; -} ngx_http_v2_srv_conf_t; - - typedef struct { size_t chunk_size; - - ngx_flag_t push_preload; - - ngx_flag_t push; - ngx_array_t *pushes; } ngx_http_v2_loc_conf_t; -extern ngx_module_t ngx_http_v2_module; - - #endif /* _NGX_HTTP_V2_MODULE_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3.c b/src/http/v3/ngx_http_v3.c new file mode 100644 index 0000000..8db229b --- /dev/null +++ b/src/http/v3/ngx_http_v3.c @@ -0,0 +1,111 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +static void ngx_http_v3_keepalive_handler(ngx_event_t *ev); +static void ngx_http_v3_cleanup_session(void *data); + + +ngx_int_t +ngx_http_v3_init_session(ngx_connection_t *c) +{ + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; + + hc = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init session"); + + h3c = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_session_t)); + if (h3c == NULL) { + goto failed; + } + + h3c->http_connection = hc; + + ngx_queue_init(&h3c->blocked); + + h3c->keepalive.log = c->log; + h3c->keepalive.data = c; + h3c->keepalive.handler = ngx_http_v3_keepalive_handler; + + h3c->table.send_insert_count.log = c->log; + h3c->table.send_insert_count.data = c; + h3c->table.send_insert_count.handler = ngx_http_v3_inc_insert_count_handler; + + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + goto failed; + } + + cln->handler = ngx_http_v3_cleanup_session; + cln->data = h3c; + + c->data = h3c; + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create http3 session"); + return NGX_ERROR; +} + + +static void +ngx_http_v3_keepalive_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + + c = ev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 keepalive handler"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "keepalive timeout"); +} + + +static void +ngx_http_v3_cleanup_session(void *data) +{ + ngx_http_v3_session_t *h3c = data; + + ngx_http_v3_cleanup_table(h3c); + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } + + if (h3c->table.send_insert_count.posted) { + ngx_delete_posted_event(&h3c->table.send_insert_count); + } +} + + +ngx_int_t +ngx_http_v3_check_flood(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + if (h3c->total_bytes / 8 > h3c->payload_bytes + 1048576) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "http3 flood detected"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "HTTP/3 flood detected"); + return NGX_ERROR; + } + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h new file mode 100644 index 0000000..9dcb5e6 --- /dev/null +++ b/src/http/v3/ngx_http_v3.h @@ -0,0 +1,160 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_H_INCLUDED_ +#define _NGX_HTTP_V3_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + +#include <ngx_http_v3_parse.h> +#include <ngx_http_v3_encode.h> +#include <ngx_http_v3_uni.h> +#include <ngx_http_v3_table.h> + + +#define NGX_HTTP_V3_ALPN_PROTO "\x02h3" +#define NGX_HTTP_V3_HQ_ALPN_PROTO "\x0Ahq-interop" +#define NGX_HTTP_V3_HQ_PROTO "hq-interop" + +#define NGX_HTTP_V3_VARLEN_INT_LEN 4 +#define NGX_HTTP_V3_PREFIX_INT_LEN 11 + +#define NGX_HTTP_V3_STREAM_CONTROL 0x00 +#define NGX_HTTP_V3_STREAM_PUSH 0x01 +#define NGX_HTTP_V3_STREAM_ENCODER 0x02 +#define NGX_HTTP_V3_STREAM_DECODER 0x03 + +#define NGX_HTTP_V3_FRAME_DATA 0x00 +#define NGX_HTTP_V3_FRAME_HEADERS 0x01 +#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03 +#define NGX_HTTP_V3_FRAME_SETTINGS 0x04 +#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05 +#define NGX_HTTP_V3_FRAME_GOAWAY 0x07 +#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d + +#define NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY 0x01 +#define NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE 0x06 +#define NGX_HTTP_V3_PARAM_BLOCKED_STREAMS 0x07 + +#define NGX_HTTP_V3_MAX_TABLE_CAPACITY 4096 + +#define NGX_HTTP_V3_STREAM_CLIENT_CONTROL 0 +#define NGX_HTTP_V3_STREAM_SERVER_CONTROL 1 +#define NGX_HTTP_V3_STREAM_CLIENT_ENCODER 2 +#define NGX_HTTP_V3_STREAM_SERVER_ENCODER 3 +#define NGX_HTTP_V3_STREAM_CLIENT_DECODER 4 +#define NGX_HTTP_V3_STREAM_SERVER_DECODER 5 +#define NGX_HTTP_V3_MAX_KNOWN_STREAM 6 +#define NGX_HTTP_V3_MAX_UNI_STREAMS 3 + +/* HTTP/3 errors */ +#define NGX_HTTP_V3_ERR_NO_ERROR 0x100 +#define NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR 0x101 +#define NGX_HTTP_V3_ERR_INTERNAL_ERROR 0x102 +#define NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR 0x103 +#define NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM 0x104 +#define NGX_HTTP_V3_ERR_FRAME_UNEXPECTED 0x105 +#define NGX_HTTP_V3_ERR_FRAME_ERROR 0x106 +#define NGX_HTTP_V3_ERR_EXCESSIVE_LOAD 0x107 +#define NGX_HTTP_V3_ERR_ID_ERROR 0x108 +#define NGX_HTTP_V3_ERR_SETTINGS_ERROR 0x109 +#define NGX_HTTP_V3_ERR_MISSING_SETTINGS 0x10a +#define NGX_HTTP_V3_ERR_REQUEST_REJECTED 0x10b +#define NGX_HTTP_V3_ERR_REQUEST_CANCELLED 0x10c +#define NGX_HTTP_V3_ERR_REQUEST_INCOMPLETE 0x10d +#define NGX_HTTP_V3_ERR_CONNECT_ERROR 0x10f +#define NGX_HTTP_V3_ERR_VERSION_FALLBACK 0x110 + +/* QPACK errors */ +#define NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED 0x200 +#define NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR 0x201 +#define NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR 0x202 + + +#define ngx_http_v3_get_session(c) \ + ((ngx_http_v3_session_t *) ((c)->quic ? (c)->quic->parent->data \ + : (c)->data)) + +#define ngx_http_quic_get_connection(c) \ + (ngx_http_v3_get_session(c)->http_connection) + +#define ngx_http_v3_get_module_loc_conf(c, module) \ + ngx_http_get_module_loc_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ + module) + +#define ngx_http_v3_get_module_srv_conf(c, module) \ + ngx_http_get_module_srv_conf(ngx_http_quic_get_connection(c)->conf_ctx, \ + module) + +#define ngx_http_v3_finalize_connection(c, code, reason) \ + ngx_quic_finalize_connection((c)->quic ? (c)->quic->parent : (c), \ + code, reason) + +#define ngx_http_v3_shutdown_connection(c, code, reason) \ + ngx_quic_shutdown_connection((c)->quic ? (c)->quic->parent : (c), \ + code, reason) + + +typedef struct { + ngx_flag_t enable; + ngx_flag_t enable_hq; + size_t max_table_capacity; + ngx_uint_t max_blocked_streams; + ngx_uint_t max_concurrent_streams; + ngx_quic_conf_t quic; +} ngx_http_v3_srv_conf_t; + + +struct ngx_http_v3_parse_s { + size_t header_limit; + ngx_http_v3_parse_headers_t headers; + ngx_http_v3_parse_data_t body; + ngx_array_t *cookies; +}; + + +struct ngx_http_v3_session_s { + ngx_http_connection_t *http_connection; + + ngx_http_v3_dynamic_table_t table; + + ngx_event_t keepalive; + ngx_uint_t nrequests; + + ngx_queue_t blocked; + ngx_uint_t nblocked; + + uint64_t next_request_id; + + off_t total_bytes; + off_t payload_bytes; + + unsigned goaway:1; + unsigned hq:1; + + ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; +}; + + +void ngx_http_v3_init_stream(ngx_connection_t *c); +void ngx_http_v3_reset_stream(ngx_connection_t *c); +ngx_int_t ngx_http_v3_init_session(ngx_connection_t *c); +ngx_int_t ngx_http_v3_check_flood(ngx_connection_t *c); +ngx_int_t ngx_http_v3_init(ngx_connection_t *c); +void ngx_http_v3_shutdown(ngx_connection_t *c); + +ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r); +ngx_int_t ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r); + + +extern ngx_module_t ngx_http_v3_module; + + +#endif /* _NGX_HTTP_V3_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_encode.c b/src/http/v3/ngx_http_v3_encode.c new file mode 100644 index 0000000..fb089c4 --- /dev/null +++ b/src/http/v3/ngx_http_v3_encode.c @@ -0,0 +1,304 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +uintptr_t +ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value) +{ + if (value <= 63) { + if (p == NULL) { + return 1; + } + + *p++ = value; + return (uintptr_t) p; + } + + if (value <= 16383) { + if (p == NULL) { + return 2; + } + + *p++ = 0x40 | (value >> 8); + *p++ = value; + return (uintptr_t) p; + } + + if (value <= 1073741823) { + if (p == NULL) { + return 4; + } + + *p++ = 0x80 | (value >> 24); + *p++ = (value >> 16); + *p++ = (value >> 8); + *p++ = value; + return (uintptr_t) p; + } + + if (p == NULL) { + return 8; + } + + *p++ = 0xc0 | (value >> 56); + *p++ = (value >> 48); + *p++ = (value >> 40); + *p++ = (value >> 32); + *p++ = (value >> 24); + *p++ = (value >> 16); + *p++ = (value >> 8); + *p++ = value; + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, ngx_uint_t prefix) +{ + ngx_uint_t thresh, n; + + thresh = (1 << prefix) - 1; + + if (value < thresh) { + if (p == NULL) { + return 1; + } + + *p++ |= value; + return (uintptr_t) p; + } + + value -= thresh; + + if (p == NULL) { + for (n = 2; value >= 128; n++) { + value >>= 7; + } + + return n; + } + + *p++ |= thresh; + + while (value >= 128) { + *p++ = 0x80 | value; + value >>= 7; + } + + *p++ = value; + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_field_section_prefix(u_char *p, ngx_uint_t insert_count, + ngx_uint_t sign, ngx_uint_t delta_base) +{ + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, insert_count, 8) + + ngx_http_v3_encode_prefix_int(NULL, delta_base, 7); + } + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, insert_count, 8); + + *p = sign ? 0x80 : 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, delta_base, 7); + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index) +{ + /* Indexed Field Line */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 6); + } + + *p = dynamic ? 0x80 : 0xc0; + + return ngx_http_v3_encode_prefix_int(p, index, 6); +} + + +uintptr_t +ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, ngx_uint_t index, + u_char *data, size_t len) +{ + size_t hlen; + u_char *p1, *p2; + + /* Literal Field Line With Name Reference */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 4) + + ngx_http_v3_encode_prefix_int(NULL, len, 7) + + len; + } + + *p = dynamic ? 0x40 : 0x50; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 4); + + p1 = p; + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); + + if (data) { + p2 = p; + hlen = ngx_http_huff_encode(data, len, p, 0); + + if (hlen) { + p = p1; + *p = 0x80; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + p = ngx_cpymem(p, data, len); + } + } + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, ngx_str_t *value) +{ + size_t hlen; + u_char *p1, *p2; + + /* Literal Field Line With Literal Name */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, name->len, 3) + + name->len + + ngx_http_v3_encode_prefix_int(NULL, value->len, 7) + + value->len; + } + + p1 = p; + *p = 0x20; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, name->len, 3); + + p2 = p; + hlen = ngx_http_huff_encode(name->data, name->len, p, 1); + + if (hlen) { + p = p1; + *p = 0x28; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 3); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + ngx_strlow(p, name->data, name->len); + p += name->len; + } + + p1 = p; + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, value->len, 7); + + p2 = p; + hlen = ngx_http_huff_encode(value->data, value->len, p, 0); + + if (hlen) { + p = p1; + *p = 0x80; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + p = ngx_cpymem(p, value->data, value->len); + } + + return (uintptr_t) p; +} + + +uintptr_t +ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index) +{ + /* Indexed Field Line With Post-Base Index */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 4); + } + + *p = 0x10; + + return ngx_http_v3_encode_prefix_int(p, index, 4); +} + + +uintptr_t +ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, u_char *data, + size_t len) +{ + size_t hlen; + u_char *p1, *p2; + + /* Literal Field Line With Post-Base Name Reference */ + + if (p == NULL) { + return ngx_http_v3_encode_prefix_int(NULL, index, 3) + + ngx_http_v3_encode_prefix_int(NULL, len, 7) + + len; + } + + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, index, 3); + + p1 = p; + *p = 0; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, len, 7); + + if (data) { + p2 = p; + hlen = ngx_http_huff_encode(data, len, p, 0); + + if (hlen) { + p = p1; + *p = 0x80; + p = (u_char *) ngx_http_v3_encode_prefix_int(p, hlen, 7); + + if (p != p2) { + ngx_memmove(p, p2, hlen); + } + + p += hlen; + + } else { + p = ngx_cpymem(p, data, len); + } + } + + return (uintptr_t) p; +} diff --git a/src/http/v3/ngx_http_v3_encode.h b/src/http/v3/ngx_http_v3_encode.h new file mode 100644 index 0000000..fca376d --- /dev/null +++ b/src/http/v3/ngx_http_v3_encode.h @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_ENCODE_H_INCLUDED_ +#define _NGX_HTTP_V3_ENCODE_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +uintptr_t ngx_http_v3_encode_varlen_int(u_char *p, uint64_t value); +uintptr_t ngx_http_v3_encode_prefix_int(u_char *p, uint64_t value, + ngx_uint_t prefix); + +uintptr_t ngx_http_v3_encode_field_section_prefix(u_char *p, + ngx_uint_t insert_count, ngx_uint_t sign, ngx_uint_t delta_base); +uintptr_t ngx_http_v3_encode_field_ri(u_char *p, ngx_uint_t dynamic, + ngx_uint_t index); +uintptr_t ngx_http_v3_encode_field_lri(u_char *p, ngx_uint_t dynamic, + ngx_uint_t index, u_char *data, size_t len); +uintptr_t ngx_http_v3_encode_field_l(u_char *p, ngx_str_t *name, + ngx_str_t *value); +uintptr_t ngx_http_v3_encode_field_pbi(u_char *p, ngx_uint_t index); +uintptr_t ngx_http_v3_encode_field_lpbi(u_char *p, ngx_uint_t index, + u_char *data, size_t len); + + +#endif /* _NGX_HTTP_V3_ENCODE_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_filter_module.c b/src/http/v3/ngx_http_v3_filter_module.c new file mode 100644 index 0000000..4d2276d --- /dev/null +++ b/src/http/v3/ngx_http_v3_filter_module.c @@ -0,0 +1,852 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +/* static table indices */ +#define NGX_HTTP_V3_HEADER_AUTHORITY 0 +#define NGX_HTTP_V3_HEADER_PATH_ROOT 1 +#define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4 +#define NGX_HTTP_V3_HEADER_DATE 6 +#define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10 +#define NGX_HTTP_V3_HEADER_LOCATION 12 +#define NGX_HTTP_V3_HEADER_METHOD_GET 17 +#define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22 +#define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23 +#define NGX_HTTP_V3_HEADER_STATUS_200 25 +#define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31 +#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 +#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 +#define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72 +#define NGX_HTTP_V3_HEADER_SERVER 92 +#define NGX_HTTP_V3_HEADER_USER_AGENT 95 + + +typedef struct { + ngx_chain_t *free; + ngx_chain_t *busy; +} ngx_http_v3_filter_ctx_t; + + +static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, + ngx_chain_t *in); +static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, + ngx_http_v3_filter_ctx_t *ctx); +static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf); + + +static ngx_http_module_t ngx_http_v3_filter_module_ctx = { + NULL, /* preconfiguration */ + ngx_http_v3_filter_init, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + NULL, /* create server configuration */ + NULL, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_filter_module = { + NGX_MODULE_V1, + &ngx_http_v3_filter_module_ctx, /* module context */ + NULL, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_output_header_filter_pt ngx_http_next_header_filter; +static ngx_http_output_body_filter_pt ngx_http_next_body_filter; + + +static ngx_int_t +ngx_http_v3_header_filter(ngx_http_request_t *r) +{ + u_char *p; + size_t len, n; + ngx_buf_t *b; + ngx_str_t host, location; + ngx_uint_t i, port; + ngx_chain_t *out, *hl, *cl, **ll; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_filter_ctx_t *ctx; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + u_char addr[NGX_SOCKADDR_STRLEN]; + + if (r->http_version != NGX_HTTP_VERSION_30) { + return ngx_http_next_header_filter(r); + } + + if (r->header_sent) { + return NGX_OK; + } + + r->header_sent = 1; + + if (r != r->main) { + return NGX_OK; + } + + h3c = ngx_http_v3_get_session(r->connection); + + if (r->method == NGX_HTTP_HEAD) { + r->header_only = 1; + } + + if (r->headers_out.last_modified_time != -1) { + if (r->headers_out.status != NGX_HTTP_OK + && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT + && r->headers_out.status != NGX_HTTP_NOT_MODIFIED) + { + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + } + } + + if (r->headers_out.status == NGX_HTTP_NO_CONTENT) { + r->header_only = 1; + ngx_str_null(&r->headers_out.content_type); + r->headers_out.last_modified_time = -1; + r->headers_out.last_modified = NULL; + r->headers_out.content_length = NULL; + r->headers_out.content_length_n = -1; + } + + if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) { + r->header_only = 1; + } + + c = r->connection; + + out = NULL; + ll = &out; + + len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + if (r->headers_out.status == NGX_HTTP_OK) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200); + + } else { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + if (r->headers_out.server == NULL) { + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + n = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + n = sizeof(NGINX_VER_BUILD) - 1; + + } else { + n = sizeof("nginx") - 1; + } + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SERVER, + NULL, n); + } + + if (r->headers_out.date == NULL) { + len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE, + NULL, ngx_cached_http_time.len); + } + + if (r->headers_out.content_type.len) { + n = r->headers_out.content_type.len; + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + n += sizeof("; charset=") - 1 + r->headers_out.charset.len; + } + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + NULL, n); + } + + if (r->headers_out.content_length == NULL) { + if (r->headers_out.content_length_n > 0) { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, + NULL, NGX_OFF_T_LEN); + + } else if (r->headers_out.content_length_n == 0) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); + } + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, + sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); + } + + if (r->headers_out.location && r->headers_out.location->value.len) { + + if (r->headers_out.location->value.data[0] == '/' + && clcf->absolute_redirect) + { + if (clcf->server_name_in_redirect) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + host = cscf->server_name; + + } else if (r->headers_in.server.len) { + host = r->headers_in.server; + + } else { + host.len = NGX_SOCKADDR_STRLEN; + host.data = addr; + + if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { + return NGX_ERROR; + } + } + + port = ngx_inet_get_port(c->local_sockaddr); + + location.len = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (clcf->port_in_redirect) { + port = (port == 443) ? 0 : port; + + } else { + port = 0; + } + + if (port) { + location.len += sizeof(":65535") - 1; + } + + location.data = ngx_pnalloc(r->pool, location.len); + if (location.data == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(location.data, "https://", sizeof("https://") - 1); + p = ngx_cpymem(p, host.data, host.len); + + if (port) { + p = ngx_sprintf(p, ":%ui", port); + } + + p = ngx_cpymem(p, r->headers_out.location->value.data, + r->headers_out.location->value.len); + + /* update r->headers_out.location->value for possible logging */ + + r->headers_out.location->value.len = p - location.data; + r->headers_out.location->value.data = location.data; + ngx_str_set(&r->headers_out.location->key, "Location"); + } + + r->headers_out.location->hash = 0; + + len += ngx_http_v3_encode_field_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LOCATION, NULL, + r->headers_out.location->value.len); + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + if (clcf->gzip_vary) { + len += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); + + } else { + r->gzip_vary = 0; + } + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, + 0, 0, 0); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \":status: %03ui\"", + r->headers_out.status); + + if (r->headers_out.status == NGX_HTTP_OK) { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200); + + } else { + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); + b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); + } + + if (r->headers_out.server == NULL) { + if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { + p = (u_char *) NGINX_VER; + n = sizeof(NGINX_VER) - 1; + + } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { + p = (u_char *) NGINX_VER_BUILD; + n = sizeof(NGINX_VER_BUILD) - 1; + + } else { + p = (u_char *) "nginx"; + n = sizeof("nginx") - 1; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"server: %*s\"", n, p); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_SERVER, + p, n); + } + + if (r->headers_out.date == NULL) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"date: %V\"", + &ngx_cached_http_time); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_DATE, + ngx_cached_http_time.data, + ngx_cached_http_time.len); + } + + if (r->headers_out.content_type.len) { + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + n = r->headers_out.content_type.len + sizeof("; charset=") - 1 + + r->headers_out.charset.len; + + p = ngx_pnalloc(r->pool, n); + if (p == NULL) { + return NGX_ERROR; + } + + p = ngx_cpymem(p, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1); + + p = ngx_cpymem(p, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* updated r->headers_out.content_type is also needed for logging */ + + r->headers_out.content_type.len = n; + r->headers_out.content_type.data = p - n; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"content-type: %V\"", + &r->headers_out.content_type); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + r->headers_out.content_type.data, + r->headers_out.content_type.len); + } + + if (r->headers_out.content_length == NULL + && r->headers_out.content_length_n >= 0) + { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"content-length: %O\"", + r->headers_out.content_length_n); + + if (r->headers_out.content_length_n > 0) { + p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); + n = p - b->last; + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, + NULL, n); + + b->last = ngx_sprintf(b->last, "%O", + r->headers_out.content_length_n); + + } else { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); + } + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + n = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1; + + p = ngx_pnalloc(r->pool, n); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_http_time(p, r->headers_out.last_modified_time); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"last-modified: %*s\"", n, p); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, + p, n); + } + + if (r->headers_out.location && r->headers_out.location->value.len) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"location: %V\"", + &r->headers_out.location->value); + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LOCATION, + r->headers_out.location->value.data, + r->headers_out.location->value.len); + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"vary: Accept-Encoding\""); + + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); + } +#endif + + part = &r->headers_out.headers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 output header: \"%V: %V\"", + &header[i].key, &header[i].value); + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + } + + if (r->header_only) { + b->last_buf = 1; + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + n = b->last - b->pos; + + h3c->payload_bytes += n; + + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl = ngx_alloc_chain_link(r->pool); + if (hl == NULL) { + return NGX_ERROR; + } + + hl->buf = b; + hl->next = cl; + + *ll = hl; + ll = &cl->next; + + if (r->headers_out.content_length_n >= 0 + && !r->header_only && !r->expect_trailers) + { + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + + ngx_http_v3_encode_varlen_int(NULL, + r->headers_out.content_length_n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + r->headers_out.content_length_n); + + h3c->payload_bytes += r->headers_out.content_length_n; + h3c->total_bytes += r->headers_out.content_length_n; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + *ll = cl; + + } else { + ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_filter_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module); + } + + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + r->header_size += cl->buf->last - cl->buf->pos; + } + + return ngx_http_write_filter(r, out); +} + + +static ngx_int_t +ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + u_char *chunk; + off_t size; + ngx_int_t rc; + ngx_buf_t *b; + ngx_chain_t *out, *cl, *tl, **ll; + ngx_http_v3_session_t *h3c; + ngx_http_v3_filter_ctx_t *ctx; + + if (in == NULL) { + return ngx_http_next_body_filter(r, in); + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_v3_filter_module); + if (ctx == NULL) { + return ngx_http_next_body_filter(r, in); + } + + h3c = ngx_http_v3_get_session(r->connection); + + out = NULL; + ll = &out; + + size = 0; + cl = in; + + for ( ;; ) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 chunk: %O", ngx_buf_size(cl->buf)); + + size += ngx_buf_size(cl->buf); + + if (cl->buf->flush + || cl->buf->sync + || ngx_buf_in_memory(cl->buf) + || cl->buf->in_file) + { + tl = ngx_alloc_chain_link(r->pool); + if (tl == NULL) { + return NGX_ERROR; + } + + tl->buf = cl->buf; + *ll = tl; + ll = &tl->next; + } + + if (cl->next == NULL) { + break; + } + + cl = cl->next; + } + + if (size) { + tl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (tl == NULL) { + return NGX_ERROR; + } + + b = tl->buf; + chunk = b->start; + + if (chunk == NULL) { + chunk = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); + if (chunk == NULL) { + return NGX_ERROR; + } + + b->start = chunk; + b->end = chunk + NGX_HTTP_V3_VARLEN_INT_LEN * 2; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->temporary = 1; + b->pos = chunk; + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size); + + tl->next = out; + out = tl; + + h3c->payload_bytes += size; + } + + if (cl->buf->last_buf) { + tl = ngx_http_v3_create_trailers(r, ctx); + if (tl == NULL) { + return NGX_ERROR; + } + + cl->buf->last_buf = 0; + + *ll = tl; + + } else { + *ll = NULL; + } + + for (cl = out; cl; cl = cl->next) { + h3c->total_bytes += cl->buf->last - cl->buf->pos; + } + + rc = ngx_http_next_body_filter(r, out); + + ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, + (ngx_buf_tag_t) &ngx_http_v3_filter_module); + + return rc; +} + + +static ngx_chain_t * +ngx_http_v3_create_trailers(ngx_http_request_t *r, + ngx_http_v3_filter_ctx_t *ctx) +{ + size_t len, n; + u_char *p; + ngx_buf_t *b; + ngx_uint_t i; + ngx_chain_t *cl, *hl; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(r->connection); + + len = 0; + + part = &r->headers_out.trailers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + len += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + + cl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (cl == NULL) { + return NULL; + } + + b = cl->buf; + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->last_buf = 1; + + if (len == 0) { + b->temporary = 0; + b->pos = b->last = NULL; + return cl; + } + + b->temporary = 1; + + len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + b->pos = ngx_palloc(r->pool, len); + if (b->pos == NULL) { + return NULL; + } + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->pos, + 0, 0, 0); + + part = &r->headers_out.trailers.part; + header = part->elts; + + for (i = 0; /* void */; i++) { + + if (i >= part->nelts) { + if (part->next == NULL) { + break; + } + + part = part->next; + header = part->elts; + i = 0; + } + + if (header[i].hash == 0) { + continue; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 output trailer: \"%V: %V\"", + &header[i].key, &header[i].value); + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + } + + n = b->last - b->pos; + + h3c->payload_bytes += n; + + hl = ngx_chain_get_free_buf(r->pool, &ctx->free); + if (hl == NULL) { + return NULL; + } + + b = hl->buf; + p = b->start; + + if (p == NULL) { + p = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); + if (p == NULL) { + return NULL; + } + + b->start = p; + b->end = p + NGX_HTTP_V3_VARLEN_INT_LEN * 2; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; + b->memory = 0; + b->temporary = 1; + b->pos = p; + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(p, + NGX_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl->next = cl; + + return hl; +} + + +static ngx_int_t +ngx_http_v3_filter_init(ngx_conf_t *cf) +{ + ngx_http_next_header_filter = ngx_http_top_header_filter; + ngx_http_top_header_filter = ngx_http_v3_header_filter; + + ngx_http_next_body_filter = ngx_http_top_body_filter; + ngx_http_top_body_filter = ngx_http_v3_body_filter; + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3_module.c b/src/http/v3/ngx_http_v3_module.c new file mode 100644 index 0000000..139bd65 --- /dev/null +++ b/src/http/v3/ngx_http_v3_module.c @@ -0,0 +1,393 @@ + +/* + * Copyright (C) Nginx, Inc. + * Copyright (C) Roman Arutyunyan + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +static ngx_int_t ngx_http_v3_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_http_v3_add_variables(ngx_conf_t *cf); +static void *ngx_http_v3_create_srv_conf(ngx_conf_t *cf); +static char *ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, + void *child); +static char *ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); + + +static ngx_command_t ngx_http_v3_commands[] = { + + { ngx_string("http3"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, enable), + NULL }, + + { ngx_string("http3_hq"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, enable_hq), + NULL }, + + { ngx_string("http3_max_concurrent_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, max_concurrent_streams), + NULL }, + + { ngx_string("http3_stream_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.stream_buffer_size), + NULL }, + + { ngx_string("quic_retry"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.retry), + NULL }, + + { ngx_string("quic_gso"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.gso_enabled), + NULL }, + + { ngx_string("quic_host_key"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_http_quic_host_key, + NGX_HTTP_SRV_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("quic_active_connection_id_limit"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_SRV_CONF_OFFSET, + offsetof(ngx_http_v3_srv_conf_t, quic.active_connection_id_limit), + NULL }, + + ngx_null_command +}; + + +static ngx_http_module_t ngx_http_v3_module_ctx = { + ngx_http_v3_add_variables, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_http_v3_create_srv_conf, /* create server configuration */ + ngx_http_v3_merge_srv_conf, /* merge server configuration */ + + NULL, /* create location configuration */ + NULL /* merge location configuration */ +}; + + +ngx_module_t ngx_http_v3_module = { + NGX_MODULE_V1, + &ngx_http_v3_module_ctx, /* module context */ + ngx_http_v3_commands, /* module directives */ + NGX_HTTP_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static ngx_http_variable_t ngx_http_v3_vars[] = { + + { ngx_string("http3"), NULL, ngx_http_v3_variable, 0, 0, 0 }, + + ngx_http_null_variable +}; + +static ngx_str_t ngx_http_quic_salt = ngx_string("ngx_quic"); + + +static ngx_int_t +ngx_http_v3_variable(ngx_http_request_t *r, + ngx_http_variable_value_t *v, uintptr_t data) +{ + ngx_http_v3_session_t *h3c; + + if (r->connection->quic) { + h3c = ngx_http_v3_get_session(r->connection); + + if (h3c->hq) { + v->len = sizeof("hq") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "hq"; + + return NGX_OK; + } + + v->len = sizeof("h3") - 1; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = (u_char *) "h3"; + + return NGX_OK; + } + + *v = ngx_http_variable_null_value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_add_variables(ngx_conf_t *cf) +{ + ngx_http_variable_t *var, *v; + + for (v = ngx_http_v3_vars; v->name.len; v++) { + var = ngx_http_add_variable(cf, &v->name, v->flags); + if (var == NULL) { + return NGX_ERROR; + } + + var->get_handler = v->get_handler; + var->data = v->data; + } + + return NGX_OK; +} + + +static void * +ngx_http_v3_create_srv_conf(ngx_conf_t *cf) +{ + ngx_http_v3_srv_conf_t *h3scf; + + h3scf = ngx_pcalloc(cf->pool, sizeof(ngx_http_v3_srv_conf_t)); + if (h3scf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * h3scf->quic.host_key = { 0, NULL } + * h3scf->quic.stream_reject_code_uni = 0; + * h3scf->quic.disable_active_migration = 0; + * h3scf->quic.idle_timeout = 0; + * h3scf->max_blocked_streams = 0; + */ + + h3scf->enable = NGX_CONF_UNSET; + h3scf->enable_hq = NGX_CONF_UNSET; + h3scf->max_table_capacity = NGX_HTTP_V3_MAX_TABLE_CAPACITY; + h3scf->max_concurrent_streams = NGX_CONF_UNSET_UINT; + + h3scf->quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; + h3scf->quic.max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; + h3scf->quic.max_concurrent_streams_uni = NGX_HTTP_V3_MAX_UNI_STREAMS; + h3scf->quic.retry = NGX_CONF_UNSET; + h3scf->quic.gso_enabled = NGX_CONF_UNSET; + h3scf->quic.stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; + h3scf->quic.stream_reject_code_bidi = NGX_HTTP_V3_ERR_REQUEST_REJECTED; + h3scf->quic.active_connection_id_limit = NGX_CONF_UNSET_UINT; + + h3scf->quic.init = ngx_http_v3_init; + h3scf->quic.shutdown = ngx_http_v3_shutdown; + + return h3scf; +} + + +static char * +ngx_http_v3_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) +{ + ngx_http_v3_srv_conf_t *prev = parent; + ngx_http_v3_srv_conf_t *conf = child; + + ngx_http_ssl_srv_conf_t *sscf; + ngx_http_core_srv_conf_t *cscf; + + ngx_conf_merge_value(conf->enable, prev->enable, 1); + + ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0); + + ngx_conf_merge_uint_value(conf->max_concurrent_streams, + prev->max_concurrent_streams, 128); + + conf->max_blocked_streams = conf->max_concurrent_streams; + + ngx_conf_merge_size_value(conf->quic.stream_buffer_size, + prev->quic.stream_buffer_size, + 65536); + + conf->quic.max_concurrent_streams_bidi = conf->max_concurrent_streams; + + ngx_conf_merge_value(conf->quic.retry, prev->quic.retry, 0); + ngx_conf_merge_value(conf->quic.gso_enabled, prev->quic.gso_enabled, 0); + + ngx_conf_merge_str_value(conf->quic.host_key, prev->quic.host_key, ""); + + ngx_conf_merge_uint_value(conf->quic.active_connection_id_limit, + prev->quic.active_connection_id_limit, + 2); + + if (conf->quic.host_key.len == 0) { + + conf->quic.host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; + conf->quic.host_key.data = ngx_palloc(cf->pool, + conf->quic.host_key.len); + if (conf->quic.host_key.data == NULL) { + return NGX_CONF_ERROR; + } + + if (RAND_bytes(conf->quic.host_key.data, NGX_QUIC_DEFAULT_HOST_KEY_LEN) + <= 0) + { + return NGX_CONF_ERROR; + } + } + + if (ngx_quic_derive_key(cf->log, "av_token_key", + &conf->quic.host_key, &ngx_http_quic_salt, + conf->quic.av_token_key, NGX_QUIC_AV_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + if (ngx_quic_derive_key(cf->log, "sr_token_key", + &conf->quic.host_key, &ngx_http_quic_salt, + conf->quic.sr_token_key, NGX_QUIC_SR_KEY_LEN) + != NGX_OK) + { + return NGX_CONF_ERROR; + } + + cscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_core_module); + conf->quic.handshake_timeout = cscf->client_header_timeout; + + sscf = ngx_http_conf_get_module_srv_conf(cf, ngx_http_ssl_module); + conf->quic.ssl = &sscf->ssl; + + return NGX_CONF_OK; +} + + +static char * +ngx_http_quic_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_v3_srv_conf_t *h3scf = conf; + + u_char *buf; + size_t size; + ssize_t n; + ngx_str_t *value; + ngx_file_t file; + ngx_file_info_t fi; + ngx_quic_conf_t *qcf; + + qcf = &h3scf->quic; + + if (qcf->host_key.len) { + return "is duplicate"; + } + + buf = NULL; +#if (NGX_SUPPRESS_WARN) + size = 0; +#endif + + value = cf->args->elts; + + if (ngx_conf_full_name(cf->cycle, &value[1], 1) != NGX_OK) { + return NGX_CONF_ERROR; + } + + ngx_memzero(&file, sizeof(ngx_file_t)); + file.name = value[1]; + file.log = cf->log; + + file.fd = ngx_open_file(file.name.data, NGX_FILE_RDONLY, NGX_FILE_OPEN, 0); + + if (file.fd == NGX_INVALID_FILE) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, ngx_errno, + ngx_open_file_n " \"%V\" failed", &file.name); + return NGX_CONF_ERROR; + } + + if (ngx_fd_info(file.fd, &fi) == NGX_FILE_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_fd_info_n " \"%V\" failed", &file.name); + goto failed; + } + + size = ngx_file_size(&fi); + + if (size == 0) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "\"%V\" zero key size", &file.name); + goto failed; + } + + buf = ngx_pnalloc(cf->pool, size); + if (buf == NULL) { + goto failed; + } + + n = ngx_read_file(&file, buf, size, 0); + + if (n == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, ngx_errno, + ngx_read_file_n " \"%V\" failed", &file.name); + goto failed; + } + + if ((size_t) n != size) { + ngx_conf_log_error(NGX_LOG_CRIT, cf, 0, + ngx_read_file_n " \"%V\" returned only " + "%z bytes instead of %uz", &file.name, n, size); + goto failed; + } + + qcf->host_key.data = buf; + qcf->host_key.len = n; + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + return NGX_CONF_OK; + +failed: + + if (ngx_close_file(file.fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, cf->log, ngx_errno, + ngx_close_file_n " \"%V\" failed", &file.name); + } + + if (buf) { + ngx_explicit_memzero(buf, size); + } + + return NGX_CONF_ERROR; +} diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c new file mode 100644 index 0000000..436765c --- /dev/null +++ b/src/http/v3/ngx_http_v3_parse.c @@ -0,0 +1,1936 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +#define ngx_http_v3_is_v2_frame(type) \ + ((type) == 0x02 || (type) == 0x06 || (type) == 0x08 || (type) == 0x09) + + +static void ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, + ngx_uint_t n); +static void ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, + ngx_uint_t *n); +static ngx_int_t ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length); + +static ngx_int_t ngx_http_v3_parse_varlen_int(ngx_connection_t *c, + ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_prefix_int(ngx_connection_t *c, + ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, + ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_rep(ngx_connection_t *c, + ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_literal(ngx_connection_t *c, + ngx_http_v3_parse_literal_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_ri(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_lri(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_l(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_pbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_control(ngx_connection_t *c, + ngx_http_v3_parse_control_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_settings(ngx_connection_t *c, + ngx_http_v3_parse_settings_t *st, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_encoder(ngx_connection_t *c, + ngx_http_v3_parse_encoder_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_inr(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_parse_field_iln(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_decoder(ngx_connection_t *c, + ngx_http_v3_parse_decoder_t *st, ngx_buf_t *b); + +static ngx_int_t ngx_http_v3_parse_lookup(ngx_connection_t *c, + ngx_uint_t dynamic, ngx_uint_t index, ngx_str_t *name, ngx_str_t *value); + + +static void +ngx_http_v3_parse_start_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t n) +{ + *loc = *b; + + if ((size_t) (loc->last - loc->pos) > n) { + loc->last = loc->pos + n; + } +} + + +static void +ngx_http_v3_parse_end_local(ngx_buf_t *b, ngx_buf_t *loc, ngx_uint_t *pn) +{ + *pn -= loc->pos - b->pos; + b->pos = loc->pos; +} + + +static ngx_int_t +ngx_http_v3_parse_skip(ngx_buf_t *b, ngx_uint_t *length) +{ + if ((size_t) (b->last - b->pos) < *length) { + *length -= b->last - b->pos; + b->pos = b->last; + return NGX_AGAIN; + } + + b->pos += *length; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_varlen_int(ngx_connection_t *c, + ngx_http_v3_parse_varlen_int_t *st, ngx_buf_t *b) +{ + u_char ch; + enum { + sw_start = 0, + sw_length_2, + sw_length_3, + sw_length_4, + sw_length_5, + sw_length_6, + sw_length_7, + sw_length_8 + }; + + for ( ;; ) { + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos++; + + switch (st->state) { + + case sw_start: + + st->value = ch; + if (st->value & 0xc0) { + st->state = sw_length_2; + break; + } + + goto done; + + case sw_length_2: + + st->value = (st->value << 8) + ch; + if ((st->value & 0xc000) == 0x4000) { + st->value &= 0x3fff; + goto done; + } + + st->state = sw_length_3; + break; + + case sw_length_4: + + st->value = (st->value << 8) + ch; + if ((st->value & 0xc0000000) == 0x80000000) { + st->value &= 0x3fffffff; + goto done; + } + + st->state = sw_length_5; + break; + + case sw_length_3: + case sw_length_5: + case sw_length_6: + case sw_length_7: + + st->value = (st->value << 8) + ch; + st->state++; + break; + + case sw_length_8: + + st->value = (st->value << 8) + ch; + st->value &= 0x3fffffffffffffff; + goto done; + } + } + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse varlen int %uL", st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_prefix_int(ngx_connection_t *c, + ngx_http_v3_parse_prefix_int_t *st, ngx_uint_t prefix, ngx_buf_t *b) +{ + u_char ch; + ngx_uint_t mask; + enum { + sw_start = 0, + sw_value + }; + + for ( ;; ) { + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos++; + + switch (st->state) { + + case sw_start: + + mask = (1 << prefix) - 1; + st->value = ch & mask; + + if (st->value != mask) { + goto done; + } + + st->shift = 0; + st->state = sw_value; + break; + + case sw_value: + + st->value += (uint64_t) (ch & 0x7f) << st->shift; + + if (st->shift == 56 + && ((ch & 0x80) || (st->value & 0xc000000000000000))) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded integer size limit"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + + if (ch & 0x80) { + st->shift += 7; + break; + } + + goto done; + } + } + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse prefix int %uL", st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_headers(ngx_connection_t *c, ngx_http_v3_parse_headers_t *st, + ngx_buf_t *b) +{ + ngx_buf_t loc; + ngx_int_t rc; + enum { + sw_start = 0, + sw_type, + sw_length, + sw_skip, + sw_prefix, + sw_verify, + sw_field_rep, + sw_done + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse headers"); + + st->state = sw_type; + + /* fall through */ + + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->type = st->vlint.value; + + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_DATA + || st->type == NGX_HTTP_V3_FRAME_GOAWAY + || st->type == NGX_HTTP_V3_FRAME_SETTINGS + || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID + || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + + st->state = sw_length; + break; + + case sw_length: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->length = st->vlint.value; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse headers type:%ui, len:%ui", + st->type, st->length); + + if (st->type != NGX_HTTP_V3_FRAME_HEADERS) { + st->state = st->length > 0 ? sw_skip : sw_type; + break; + } + + if (st->length == 0) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + + st->state = sw_prefix; + break; + + case sw_skip: + + rc = ngx_http_v3_parse_skip(b, &st->length); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_type; + break; + + case sw_prefix: + + ngx_http_v3_parse_start_local(b, &loc, st->length); + + rc = ngx_http_v3_parse_field_section_prefix(c, &st->prefix, &loc); + + ngx_http_v3_parse_end_local(b, &loc, &st->length); + + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_verify; + break; + + case sw_verify: + + rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_field_rep; + + /* fall through */ + + case sw_field_rep: + + ngx_http_v3_parse_start_local(b, &loc, st->length); + + rc = ngx_http_v3_parse_field_rep(c, &st->field_rep, st->prefix.base, + &loc); + + ngx_http_v3_parse_end_local(b, &loc, &st->length); + + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_FRAME_ERROR; + } + + if (rc != NGX_DONE) { + return rc; + } + + if (st->length == 0) { + goto done; + } + + return NGX_OK; + } + } + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done"); + + if (st->prefix.insert_count > 0) { + if (ngx_http_v3_send_ack_section(c, c->quic->id) != NGX_OK) { + return NGX_ERROR; + } + + ngx_http_v3_ack_insert_count(c, st->prefix.insert_count); + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_section_prefix(ngx_connection_t *c, + ngx_http_v3_parse_field_section_prefix_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_req_insert_count, + sw_delta_base, + sw_read_delta_base + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field section prefix"); + + st->state = sw_req_insert_count; + + /* fall through */ + + case sw_req_insert_count: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 8, b); + if (rc != NGX_DONE) { + return rc; + } + + st->insert_count = st->pint.value; + st->state = sw_delta_base; + break; + + case sw_delta_base: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->sign = (ch & 0x80) ? 1 : 0; + st->state = sw_read_delta_base; + + /* fall through */ + + case sw_read_delta_base: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->delta_base = st->pint.value; + goto done; + } + } + +done: + + rc = ngx_http_v3_decode_insert_count(c, &st->insert_count); + if (rc != NGX_OK) { + return rc; + } + + if (st->sign) { + if (st->insert_count <= st->delta_base) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent negative base"); + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + st->base = st->insert_count - st->delta_base - 1; + + } else { + st->base = st->insert_count + st->delta_base; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field section prefix done " + "insert_count:%ui, sign:%ui, delta_base:%ui, base:%ui", + st->insert_count, st->sign, st->delta_base, st->base); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_rep(ngx_connection_t *c, + ngx_http_v3_parse_field_rep_t *st, ngx_uint_t base, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_field_ri, + sw_field_lri, + sw_field_l, + sw_field_pbi, + sw_field_lpbi + }; + + if (st->state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field representation"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + ngx_memzero(&st->field, sizeof(ngx_http_v3_parse_field_t)); + + st->field.base = base; + + if (ch & 0x80) { + /* Indexed Field Line */ + + st->state = sw_field_ri; + + } else if (ch & 0x40) { + /* Literal Field Line With Name Reference */ + + st->state = sw_field_lri; + + } else if (ch & 0x20) { + /* Literal Field Line With Literal Name */ + + st->state = sw_field_l; + + } else if (ch & 0x10) { + /* Indexed Field Line With Post-Base Index */ + + st->state = sw_field_pbi; + + } else { + /* Literal Field Line With Post-Base Name Reference */ + + st->state = sw_field_lpbi; + } + } + + switch (st->state) { + + case sw_field_ri: + rc = ngx_http_v3_parse_field_ri(c, &st->field, b); + break; + + case sw_field_lri: + rc = ngx_http_v3_parse_field_lri(c, &st->field, b); + break; + + case sw_field_l: + rc = ngx_http_v3_parse_field_l(c, &st->field, b); + break; + + case sw_field_pbi: + rc = ngx_http_v3_parse_field_pbi(c, &st->field, b); + break; + + case sw_field_lpbi: + rc = ngx_http_v3_parse_field_lpbi(c, &st->field, b); + break; + + default: + rc = NGX_OK; + } + + if (rc != NGX_DONE) { + return rc; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field representation done"); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_literal(ngx_connection_t *c, ngx_http_v3_parse_literal_t *st, + ngx_buf_t *b) +{ + u_char ch; + ngx_uint_t n; + ngx_http_core_srv_conf_t *cscf; + enum { + sw_start = 0, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse literal huff:%ui, len:%ui", + st->huffman, st->length); + + n = st->length; + + cscf = ngx_http_v3_get_module_srv_conf(c, ngx_http_core_module); + + if (n > cscf->large_client_header_buffers.size) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent too large field line"); + return NGX_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + + if (st->huffman) { + n = n * 8 / 5; + st->huffstate = 0; + } + + st->last = ngx_pnalloc(c->pool, n + 1); + if (st->last == NULL) { + return NGX_ERROR; + } + + st->value.data = st->last; + st->state = sw_value; + + /* fall through */ + + case sw_value: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos++; + + if (st->huffman) { + if (ngx_http_huff_decode(&st->huffstate, &ch, 1, &st->last, + st->length == 1, c->log) + != NGX_OK) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent invalid encoded field line"); + return NGX_ERROR; + } + + } else { + *st->last++ = ch; + } + + if (--st->length) { + break; + } + + st->value.len = st->last - st->value.data; + *st->last = '\0'; + goto done; + } + } + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse literal done \"%V\"", &st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_ri(ngx_connection_t *c, ngx_http_v3_parse_field_t *st, + ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_index + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field ri"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->dynamic = (ch & 0x40) ? 0 : 1; + st->state = sw_index; + + /* fall through */ + + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + goto done; + } + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field ri done %s%ui]", + st->dynamic ? "dynamic[-" : "static[", st->index); + + if (st->dynamic) { + st->index = st->base - st->index - 1; + } + + rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, + &st->value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_lri(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_index, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lri"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->dynamic = (ch & 0x10) ? 0 : 1; + st->state = sw_index; + + /* fall through */ + + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + st->value.data = (u_char *) ""; + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lri done %s%ui] \"%V\"", + st->dynamic ? "dynamic[-" : "static[", + st->index, &st->value); + + if (st->dynamic) { + st->index = st->base - st->index - 1; + } + + rc = ngx_http_v3_parse_lookup(c, st->dynamic, st->index, &st->name, NULL); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_l(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_name_len, + sw_name, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field l"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x08) ? 1 : 0; + st->state = sw_name_len; + + /* fall through */ + + case sw_name_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + return NGX_ERROR; + } + + st->state = sw_name; + break; + + case sw_name: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->name = st->literal.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + st->value.data = (u_char *) ""; + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field l done \"%V\" \"%V\"", + &st->name, &st->value); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_pbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_index + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field pbi"); + + st->state = sw_index; + + /* fall through */ + + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 4, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + goto done; + } + } + +done: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field pbi done dynamic[+%ui]", st->index); + + rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, + &st->value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_lpbi(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_index, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lpbi"); + + st->state = sw_index; + + /* fall through */ + + case sw_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 3, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + st->value.data = (u_char *) ""; + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field lpbi done dynamic[+%ui] \"%V\"", + st->index, &st->value); + + rc = ngx_http_v3_parse_lookup(c, 1, st->base + st->index, &st->name, NULL); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_lookup(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *name, ngx_str_t *value) +{ + u_char *p; + + if (!dynamic) { + if (ngx_http_v3_lookup_static(c, index, name, value) != NGX_OK) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + return NGX_OK; + } + + if (ngx_http_v3_lookup(c, index, name, value) != NGX_OK) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + if (name) { + p = ngx_pnalloc(c->pool, name->len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, name->data, name->len); + p[name->len] = '\0'; + name->data = p; + } + + if (value) { + p = ngx_pnalloc(c->pool, value->len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, value->data, value->len); + p[value->len] = '\0'; + value->data = p; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_parse_control(ngx_connection_t *c, ngx_http_v3_parse_control_t *st, + ngx_buf_t *b) +{ + ngx_buf_t loc; + ngx_int_t rc; + enum { + sw_start = 0, + sw_first_type, + sw_type, + sw_length, + sw_settings, + sw_skip + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse control"); + + st->state = sw_first_type; + + /* fall through */ + + case sw_first_type: + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->type = st->vlint.value; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse frame type:%ui", st->type); + + if (st->state == sw_first_type + && st->type != NGX_HTTP_V3_FRAME_SETTINGS) + { + return NGX_HTTP_V3_ERR_MISSING_SETTINGS; + } + + if (st->state != sw_first_type + && st->type == NGX_HTTP_V3_FRAME_SETTINGS) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_DATA + || st->type == NGX_HTTP_V3_FRAME_HEADERS + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + + if (st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH) { + return NGX_HTTP_V3_ERR_ID_ERROR; + } + + st->state = sw_length; + break; + + case sw_length: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse frame len:%uL", st->vlint.value); + + st->length = st->vlint.value; + if (st->length == 0) { + st->state = sw_type; + break; + } + + switch (st->type) { + + case NGX_HTTP_V3_FRAME_SETTINGS: + st->state = sw_settings; + break; + + default: + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse skip unknown frame"); + st->state = sw_skip; + } + + break; + + case sw_settings: + + ngx_http_v3_parse_start_local(b, &loc, st->length); + + rc = ngx_http_v3_parse_settings(c, &st->settings, &loc); + + ngx_http_v3_parse_end_local(b, &loc, &st->length); + + if (st->length == 0 && rc == NGX_AGAIN) { + return NGX_HTTP_V3_ERR_SETTINGS_ERROR; + } + + if (rc != NGX_DONE) { + return rc; + } + + if (st->length == 0) { + st->state = sw_type; + } + + break; + + case sw_skip: + + rc = ngx_http_v3_parse_skip(b, &st->length); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_type; + break; + } + } +} + + +static ngx_int_t +ngx_http_v3_parse_settings(ngx_connection_t *c, + ngx_http_v3_parse_settings_t *st, ngx_buf_t *b) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_id, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse settings"); + + st->state = sw_id; + + /* fall through */ + + case sw_id: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->id = st->vlint.value; + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + if (ngx_http_v3_set_param(c, st->id, st->vlint.value) != NGX_OK) { + return NGX_HTTP_V3_ERR_SETTINGS_ERROR; + } + + goto done; + } + } + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse settings done"); + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_encoder(ngx_connection_t *c, ngx_http_v3_parse_encoder_t *st, + ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_inr, + sw_iln, + sw_capacity, + sw_duplicate + }; + + for ( ;; ) { + + if (st->state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse encoder instruction"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + if (ch & 0x80) { + /* Insert With Name Reference */ + + st->state = sw_inr; + + } else if (ch & 0x40) { + /* Insert With Literal Name */ + + st->state = sw_iln; + + } else if (ch & 0x20) { + /* Set Dynamic Table Capacity */ + + st->state = sw_capacity; + + } else { + /* Duplicate */ + + st->state = sw_duplicate; + } + } + + switch (st->state) { + + case sw_inr: + + rc = ngx_http_v3_parse_field_inr(c, &st->field, b); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_start; + break; + + case sw_iln: + + rc = ngx_http_v3_parse_field_iln(c, &st->field, b); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_start; + break; + + case sw_capacity: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_set_capacity(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + + default: /* sw_duplicate */ + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_duplicate(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + } + } +} + + +static ngx_int_t +ngx_http_v3_parse_field_inr(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_name_index, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field inr"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->dynamic = (ch & 0x40) ? 0 : 1; + st->state = sw_name_index; + + /* fall through */ + + case sw_name_index: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + st->index = st->pint.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + st->value.len = 0; + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field inr done %s[%ui] \"%V\"", + st->dynamic ? "dynamic" : "static", + st->index, &st->value); + + rc = ngx_http_v3_ref_insert(c, st->dynamic, st->index, &st->value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_field_iln(ngx_connection_t *c, + ngx_http_v3_parse_field_t *st, ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_name_len, + sw_name, + sw_value_len, + sw_read_value_len, + sw_value + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field iln"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x20) ? 1 : 0; + st->state = sw_name_len; + + /* fall through */ + + case sw_name_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 5, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + return NGX_ERROR; + } + + st->state = sw_name; + break; + + case sw_name: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->name = st->literal.value; + st->state = sw_value_len; + break; + + case sw_value_len: + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + st->literal.huffman = (ch & 0x80) ? 1 : 0; + st->state = sw_read_value_len; + + /* fall through */ + + case sw_read_value_len: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + st->literal.length = st->pint.value; + if (st->literal.length == 0) { + st->value.len = 0; + goto done; + } + + st->state = sw_value; + break; + + case sw_value: + + rc = ngx_http_v3_parse_literal(c, &st->literal, b); + if (rc != NGX_DONE) { + return rc; + } + + st->value = st->literal.value; + goto done; + } + } + +done: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse field iln done \"%V\":\"%V\"", + &st->name, &st->value); + + rc = ngx_http_v3_insert(c, &st->name, &st->value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + return NGX_DONE; +} + + +static ngx_int_t +ngx_http_v3_parse_decoder(ngx_connection_t *c, ngx_http_v3_parse_decoder_t *st, + ngx_buf_t *b) +{ + u_char ch; + ngx_int_t rc; + enum { + sw_start = 0, + sw_ack_section, + sw_cancel_stream, + sw_inc_insert_count + }; + + for ( ;; ) { + + if (st->state == sw_start) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse decoder instruction"); + + if (b->pos == b->last) { + return NGX_AGAIN; + } + + ch = *b->pos; + + if (ch & 0x80) { + /* Section Acknowledgment */ + + st->state = sw_ack_section; + + } else if (ch & 0x40) { + /* Stream Cancellation */ + + st->state = sw_cancel_stream; + + } else { + /* Insert Count Increment */ + + st->state = sw_inc_insert_count; + } + } + + switch (st->state) { + + case sw_ack_section: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 7, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_ack_section(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + + case sw_cancel_stream: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_cancel_stream(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + + case sw_inc_insert_count: + + rc = ngx_http_v3_parse_prefix_int(c, &st->pint, 6, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_inc_insert_count(c, st->pint.value); + if (rc != NGX_OK) { + return rc; + } + + st->state = sw_start; + break; + } + } +} + + +ngx_int_t +ngx_http_v3_parse_data(ngx_connection_t *c, ngx_http_v3_parse_data_t *st, + ngx_buf_t *b) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_type, + sw_length, + sw_skip + }; + + for ( ;; ) { + + switch (st->state) { + + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data"); + + st->state = sw_type; + + /* fall through */ + + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->type = st->vlint.value; + + if (st->type == NGX_HTTP_V3_FRAME_HEADERS) { + /* trailers */ + goto done; + } + + if (ngx_http_v3_is_v2_frame(st->type) + || st->type == NGX_HTTP_V3_FRAME_GOAWAY + || st->type == NGX_HTTP_V3_FRAME_SETTINGS + || st->type == NGX_HTTP_V3_FRAME_MAX_PUSH_ID + || st->type == NGX_HTTP_V3_FRAME_CANCEL_PUSH + || st->type == NGX_HTTP_V3_FRAME_PUSH_PROMISE) + { + return NGX_HTTP_V3_ERR_FRAME_UNEXPECTED; + } + + st->state = sw_length; + break; + + case sw_length: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + st->length = st->vlint.value; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse data type:%ui, len:%ui", + st->type, st->length); + + if (st->type != NGX_HTTP_V3_FRAME_DATA && st->length > 0) { + st->state = sw_skip; + break; + } + + st->state = sw_type; + return NGX_OK; + + case sw_skip: + + rc = ngx_http_v3_parse_skip(b, &st->length); + if (rc != NGX_DONE) { + return rc; + } + + st->state = sw_type; + break; + } + } + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse data done"); + + st->state = sw_start; + return NGX_DONE; +} + + +ngx_int_t +ngx_http_v3_parse_uni(ngx_connection_t *c, ngx_http_v3_parse_uni_t *st, + ngx_buf_t *b) +{ + ngx_int_t rc; + enum { + sw_start = 0, + sw_type, + sw_control, + sw_encoder, + sw_decoder, + sw_unknown + }; + + for ( ;; ) { + + switch (st->state) { + case sw_start: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse uni"); + + st->state = sw_type; + + /* fall through */ + + case sw_type: + + rc = ngx_http_v3_parse_varlen_int(c, &st->vlint, b); + if (rc != NGX_DONE) { + return rc; + } + + rc = ngx_http_v3_register_uni_stream(c, st->vlint.value); + if (rc != NGX_OK) { + return rc; + } + + switch (st->vlint.value) { + case NGX_HTTP_V3_STREAM_CONTROL: + st->state = sw_control; + break; + + case NGX_HTTP_V3_STREAM_ENCODER: + st->state = sw_encoder; + break; + + case NGX_HTTP_V3_STREAM_DECODER: + st->state = sw_decoder; + break; + + default: + st->state = sw_unknown; + } + + break; + + case sw_control: + + return ngx_http_v3_parse_control(c, &st->u.control, b); + + case sw_encoder: + + return ngx_http_v3_parse_encoder(c, &st->u.encoder, b); + + case sw_decoder: + + return ngx_http_v3_parse_decoder(c, &st->u.decoder, b); + + case sw_unknown: + + b->pos = b->last; + return NGX_AGAIN; + } + } +} diff --git a/src/http/v3/ngx_http_v3_parse.h b/src/http/v3/ngx_http_v3_parse.h new file mode 100644 index 0000000..ba004db --- /dev/null +++ b/src/http/v3/ngx_http_v3_parse.h @@ -0,0 +1,146 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_PARSE_H_INCLUDED_ +#define _NGX_HTTP_V3_PARSE_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +typedef struct { + ngx_uint_t state; + uint64_t value; +} ngx_http_v3_parse_varlen_int_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t shift; + uint64_t value; +} ngx_http_v3_parse_prefix_int_t; + + +typedef struct { + ngx_uint_t state; + uint64_t id; + ngx_http_v3_parse_varlen_int_t vlint; +} ngx_http_v3_parse_settings_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t insert_count; + ngx_uint_t delta_base; + ngx_uint_t sign; + ngx_uint_t base; + ngx_http_v3_parse_prefix_int_t pint; +} ngx_http_v3_parse_field_section_prefix_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t length; + ngx_uint_t huffman; + ngx_str_t value; + u_char *last; + u_char huffstate; +} ngx_http_v3_parse_literal_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t index; + ngx_uint_t base; + ngx_uint_t dynamic; + + ngx_str_t name; + ngx_str_t value; + + ngx_http_v3_parse_prefix_int_t pint; + ngx_http_v3_parse_literal_t literal; +} ngx_http_v3_parse_field_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_field_t field; +} ngx_http_v3_parse_field_rep_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t type; + ngx_uint_t length; + ngx_http_v3_parse_varlen_int_t vlint; + ngx_http_v3_parse_field_section_prefix_t prefix; + ngx_http_v3_parse_field_rep_t field_rep; +} ngx_http_v3_parse_headers_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_field_t field; + ngx_http_v3_parse_prefix_int_t pint; +} ngx_http_v3_parse_encoder_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_prefix_int_t pint; +} ngx_http_v3_parse_decoder_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t type; + ngx_uint_t length; + ngx_http_v3_parse_varlen_int_t vlint; + ngx_http_v3_parse_settings_t settings; +} ngx_http_v3_parse_control_t; + + +typedef struct { + ngx_uint_t state; + ngx_http_v3_parse_varlen_int_t vlint; + union { + ngx_http_v3_parse_encoder_t encoder; + ngx_http_v3_parse_decoder_t decoder; + ngx_http_v3_parse_control_t control; + } u; +} ngx_http_v3_parse_uni_t; + + +typedef struct { + ngx_uint_t state; + ngx_uint_t type; + ngx_uint_t length; + ngx_http_v3_parse_varlen_int_t vlint; +} ngx_http_v3_parse_data_t; + + +/* + * Parse functions return codes: + * NGX_DONE - parsing done + * NGX_OK - sub-element done + * NGX_AGAIN - more data expected + * NGX_BUSY - waiting for external event + * NGX_ERROR - internal error + * NGX_HTTP_V3_ERROR_XXX - HTTP/3 or QPACK error + */ + +ngx_int_t ngx_http_v3_parse_headers(ngx_connection_t *c, + ngx_http_v3_parse_headers_t *st, ngx_buf_t *b); +ngx_int_t ngx_http_v3_parse_data(ngx_connection_t *c, + ngx_http_v3_parse_data_t *st, ngx_buf_t *b); +ngx_int_t ngx_http_v3_parse_uni(ngx_connection_t *c, + ngx_http_v3_parse_uni_t *st, ngx_buf_t *b); + + +#endif /* _NGX_HTTP_V3_PARSE_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c new file mode 100644 index 0000000..0faddd2 --- /dev/null +++ b/src/http/v3/ngx_http_v3_request.c @@ -0,0 +1,1718 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +static void ngx_http_v3_init_request_stream(ngx_connection_t *c); +static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); +static void ngx_http_v3_cleanup_connection(void *data); +static void ngx_http_v3_cleanup_request(void *data); +static void ngx_http_v3_process_request(ngx_event_t *rev); +static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value); +static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r); +static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r, + ngx_chain_t *in); + + +static const struct { + ngx_str_t name; + ngx_uint_t method; +} ngx_http_v3_methods[] = { + + { ngx_string("GET"), NGX_HTTP_GET }, + { ngx_string("POST"), NGX_HTTP_POST }, + { ngx_string("HEAD"), NGX_HTTP_HEAD }, + { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS }, + { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND }, + { ngx_string("PUT"), NGX_HTTP_PUT }, + { ngx_string("MKCOL"), NGX_HTTP_MKCOL }, + { ngx_string("DELETE"), NGX_HTTP_DELETE }, + { ngx_string("COPY"), NGX_HTTP_COPY }, + { ngx_string("MOVE"), NGX_HTTP_MOVE }, + { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH }, + { ngx_string("LOCK"), NGX_HTTP_LOCK }, + { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK }, + { ngx_string("PATCH"), NGX_HTTP_PATCH }, + { ngx_string("TRACE"), NGX_HTTP_TRACE }, + { ngx_string("CONNECT"), NGX_HTTP_CONNECT } +}; + + +void +ngx_http_v3_init_stream(ngx_connection_t *c) +{ + ngx_http_connection_t *hc, *phc; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_core_loc_conf_t *clcf; + + hc = c->data; + + hc->ssl = 1; + + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + + if (c->quic == NULL) { + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + h3scf->quic.idle_timeout = clcf->keepalive_timeout; + + ngx_quic_run(c, &h3scf->quic); + return; + } + + phc = ngx_http_quic_get_connection(c); + + if (phc->ssl_servername) { + hc->ssl_servername = phc->ssl_servername; +#if (NGX_PCRE) + hc->ssl_servername_regex = phc->ssl_servername_regex; +#endif + hc->conf_ctx = phc->conf_ctx; + + ngx_set_connection_log(c, clcf->error_log); + } + + if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + ngx_http_v3_init_uni_stream(c); + + } else { + ngx_http_v3_init_request_stream(c); + } +} + + +ngx_int_t +ngx_http_v3_init(ngx_connection_t *c) +{ + unsigned int len; + const unsigned char *data; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_core_loc_conf_t *clcf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); + + if (ngx_http_v3_init_session(c) != NGX_OK) { + return NGX_ERROR; + } + + h3c = ngx_http_v3_get_session(c); + clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); + ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (h3scf->enable_hq) { + if (!h3scf->enable) { + h3c->hq = 1; + return NGX_OK; + } + + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + + if (len == sizeof(NGX_HTTP_V3_HQ_PROTO) - 1 + && ngx_strncmp(data, NGX_HTTP_V3_HQ_PROTO, len) == 0) + { + h3c->hq = 1; + return NGX_OK; + } + } + + if (ngx_http_v3_send_settings(c) != NGX_OK) { + return NGX_ERROR; + } + + if (h3scf->max_table_capacity > 0) { + if (ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER) == NULL) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +void +ngx_http_v3_shutdown(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 shutdown"); + + h3c = ngx_http_v3_get_session(c); + + if (h3c == NULL) { + ngx_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "connection shutdown"); + return; + } + + if (!h3c->goaway) { + h3c->goaway = 1; + + if (!h3c->hq) { + (void) ngx_http_v3_send_goaway(c, h3c->next_request_id); + } + + ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "connection shutdown"); + } +} + + +static void +ngx_http_v3_init_request_stream(ngx_connection_t *c) +{ + uint64_t n; + ngx_event_t *rev; + ngx_pool_cleanup_t *cln; + ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream"); + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + + hc = c->data; + + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + + n = c->quic->id >> 2; + + if (n >= clcf->keepalive_requests * 2) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "too many requests per connection"); + ngx_http_close_connection(c); + return; + } + + h3c = ngx_http_v3_get_session(c); + + if (h3c->goaway) { + c->close = 1; + ngx_http_close_connection(c); + return; + } + + h3c->next_request_id = c->quic->id + 0x04; + + if (n + 1 == clcf->keepalive_requests + || ngx_current_msec - c->start_time > clcf->keepalive_time) + { + h3c->goaway = 1; + + if (!h3c->hq) { + if (ngx_http_v3_send_goaway(c, h3c->next_request_id) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + } + + ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "reached maximum number of requests"); + } + + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_v3_cleanup_connection; + cln->data = c; + + h3c->nrequests++; + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } + + rev = c->read; + + if (!h3c->hq) { + rev->handler = ngx_http_v3_wait_request_handler; + c->write->handler = ngx_http_empty_handler; + } + + if (rev->ready) { + rev->handler(rev); + return; + } + + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_reusable_connection(c, 1); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } +} + + +static void +ngx_http_v3_wait_request_handler(ngx_event_t *rev) +{ + size_t size; + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_request_t *r; + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 wait request handler"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + ngx_http_close_connection(c); + return; + } + + if (c->close) { + ngx_http_close_connection(c); + return; + } + + hc = c->data; + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + + size = cscf->client_header_buffer_size; + + b = c->buffer; + + if (b == NULL) { + b = ngx_create_temp_buf(c->pool, size); + if (b == NULL) { + ngx_http_close_connection(c); + return; + } + + c->buffer = b; + + } else if (b->start == NULL) { + + b->start = ngx_palloc(c->pool, size); + if (b->start == NULL) { + ngx_http_close_connection(c); + return; + } + + b->pos = b->start; + b->last = b->start; + b->end = b->last + size; + } + + n = c->recv(c, b->last, size); + + if (n == NGX_AGAIN) { + + if (!rev->timer_set) { + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_reusable_connection(c, 1); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + /* + * We are trying to not hold c->buffer's memory for an idle connection. + */ + + if (ngx_pfree(c->pool, b->start) == NGX_OK) { + b->start = NULL; + } + + return; + } + + if (n == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client closed connection"); + ngx_http_close_connection(c); + return; + } + + b->last += n; + + c->log->action = "reading client request"; + + ngx_reusable_connection(c, 0); + + r = ngx_http_create_request(c); + if (r == NULL) { + ngx_http_close_connection(c); + return; + } + + r->http_version = NGX_HTTP_VERSION_30; + + r->v3_parse = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_t)); + if (r->v3_parse == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + r->v3_parse->header_limit = cscf->large_client_header_buffers.size + * cscf->large_client_header_buffers.num; + + c->data = r; + c->requests = (c->quic->id >> 2) + 1; + + cln = ngx_pool_cleanup_add(r->pool, 0); + if (cln == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + cln->handler = ngx_http_v3_cleanup_request; + cln->data = r; + + rev->handler = ngx_http_v3_process_request; + ngx_http_v3_process_request(rev); +} + + +void +ngx_http_v3_reset_stream(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + if (!c->read->eof && !h3c->hq + && h3c->known_streams[NGX_HTTP_V3_STREAM_SERVER_DECODER] + && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) + { + (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); + } + + if (c->timedout) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR); + + } else if (c->close) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); + + } else if (c->requests == 0 || c->error) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR); + } +} + + +static void +ngx_http_v3_cleanup_connection(void *data) +{ + ngx_connection_t *c = data; + + ngx_http_v3_session_t *h3c; + ngx_http_core_loc_conf_t *clcf; + + h3c = ngx_http_v3_get_session(c); + + if (--h3c->nrequests == 0) { + clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); + ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); + } +} + + +static void +ngx_http_v3_cleanup_request(void *data) +{ + ngx_http_request_t *r = data; + + if (!r->response_sent) { + r->connection->error = 1; + } +} + + +static void +ngx_http_v3_process_request(ngx_event_t *rev) +{ + u_char *p; + ssize_t n; + ngx_buf_t *b; + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_v3_session_t *h3c; + ngx_http_core_srv_conf_t *cscf; + ngx_http_v3_parse_headers_t *st; + + c = rev->data; + r = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http3 process request"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + h3c = ngx_http_v3_get_session(c); + + st = &r->v3_parse->headers; + + b = r->header_in; + + for ( ;; ) { + + if (b->pos == b->last) { + + if (rev->ready) { + n = c->recv(c, b->start, b->end - b->start); + + } else { + n = NGX_AGAIN; + } + + if (n == NGX_AGAIN) { + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(r, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + break; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client prematurely closed connection"); + } + + if (n == 0 || n == NGX_ERROR) { + c->error = 1; + c->log->action = "reading client request"; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; + } + + b->pos = b->start; + b->last = b->start + n; + } + + p = b->pos; + + rc = ngx_http_v3_parse_headers(c, st, b); + + if (rc > 0) { + ngx_quic_reset_stream(c, rc); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid header"); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; + } + + if (rc == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + break; + } + + r->request_length += b->pos - p; + h3c->total_bytes += b->pos - p; + + if (ngx_http_v3_check_flood(c) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_CLOSE); + break; + } + + if (rc == NGX_BUSY) { + if (rev->error) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; + } + + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(r, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + break; + } + + if (rc == NGX_AGAIN) { + continue; + } + + /* rc == NGX_OK || rc == NGX_DONE */ + + h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL, + &st->field_rep.field.name, + &st->field_rep.field.value); + + if (ngx_http_v3_process_header(r, &st->field_rep.field.name, + &st->field_rep.field.value) + != NGX_OK) + { + break; + } + + if (rc == NGX_DONE) { + if (ngx_http_v3_process_request_header(r) != NGX_OK) { + break; + } + + ngx_http_process_request(r); + break; + } + } + + ngx_http_run_posted_requests(c); + + return; +} + + +static ngx_int_t +ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + size_t len; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_srv_conf_t *cscf; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + len = name->len + value->len; + + if (len > r->v3_parse->header_limit) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too large header"); + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); + return NGX_ERROR; + } + + r->v3_parse->header_limit -= len; + + if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%V\"", name); + + return NGX_OK; + } + } + + if (name->len && name->data[0] == ':') { + return ngx_http_v3_process_pseudo_header(r, name, value); + } + + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + if (name->len == cookie.len + && ngx_memcmp(name->data, cookie.data, cookie.len) == 0) + { + if (ngx_http_v3_cookie(r, value) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + } else { + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + h->key = *name; + h->value = *value; + h->lowcase_key = h->key.data; + h->hash = ngx_hash_key(h->key.data, h->key.len); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \"%V: %V\"", name, value); + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + u_char ch; + ngx_uint_t i; + ngx_http_core_srv_conf_t *cscf; + + r->invalid_header = 0; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + for (i = (name->data[0] == ':'); i != name->len; i++) { + ch = name->data[i]; + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + if (ch <= 0x20 || ch == 0x7f || ch == ':' + || (ch >= 'A' && ch <= 'Z')) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", name); + + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + for (i = 0; i != value->len; i++) { + ch = value->data[i]; + + if (ch == '\0' || ch == LF || ch == CR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "invalid value: \"%V\"", name, value); + + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + u_char ch, c; + ngx_uint_t i; + + if (r->request_line.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent out of order pseudo-headers"); + goto failed; + } + + if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { + + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":method\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":method\" header"); + goto failed; + } + + r->method_name = *value; + + for (i = 0; i < sizeof(ngx_http_v3_methods) + / sizeof(ngx_http_v3_methods[0]); i++) + { + if (value->len == ngx_http_v3_methods[i].name.len + && ngx_strncmp(value->data, + ngx_http_v3_methods[i].name.data, value->len) + == 0) + { + r->method = ngx_http_v3_methods[i].method; + break; + } + } + + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", value); + goto failed; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 method \"%V\" %ui", value, r->method); + return NGX_OK; + } + + if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { + + if (r->uri_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":path\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":path\" header"); + goto failed; + } + + r->uri_start = value->data; + r->uri_end = value->data + value->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid \":path\" header: \"%V\"", + value); + goto failed; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 path \"%V\"", value); + return NGX_OK; + } + + if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { + + if (r->schema.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":scheme\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":scheme\" header"); + goto failed; + } + + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + continue; + } + + if (((ch >= '0' && ch <= '9') + || ch == '+' || ch == '-' || ch == '.') + && i > 0) + { + continue; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid \":scheme\" header: \"%V\"", + value); + goto failed; + } + + r->schema = *value; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 schema \"%V\"", value); + return NGX_OK; + } + + if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { + + if (r->host_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":authority\" header"); + goto failed; + } + + r->host_start = value->data; + r->host_end = value->data + value->len; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 authority \"%V\"", value); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent unknown pseudo-header \"%V\"", name); + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) +{ + size_t len; + u_char *p; + ngx_int_t rc; + ngx_str_t host; + + if (r->request_line.len) { + return NGX_OK; + } + + if (r->method_name.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":method\" header"); + goto failed; + } + + if (r->schema.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":scheme\" header"); + goto failed; + } + + if (r->uri_start == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":path\" header"); + goto failed; + } + + len = r->method_name.len + 1 + + (r->uri_end - r->uri_start) + 1 + + sizeof("HTTP/3.0") - 1; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + *p++ = ' '; + p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); + *p++ = ' '; + p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1); + + r->request_line.len = p - r->request_line.data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request line: \"%V\"", &r->request_line); + + ngx_str_set(&r->http_protocol, "HTTP/3.0"); + + if (ngx_http_process_request_uri(r) != NGX_OK) { + return NGX_ERROR; + } + + if (r->host_end) { + + host.len = r->host_end - r->host_start; + host.data = r->host_start; + + rc = ngx_http_validate_host(&host, r->pool, 0); + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid host in request line"); + goto failed; + } + + if (rc == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) { + return NGX_ERROR; + } + + r->headers_in.server = host; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + return NGX_OK; + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_process_request_header(ngx_http_request_t *r) +{ + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + + c = r->connection; + + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + h3c = ngx_http_v3_get_session(c); + h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); + + if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client attempted to request the server name " + "for which the negotiated protocol is disabled"); + ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); + return NGX_ERROR; + } + + if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { + return NGX_ERROR; + } + + if (r->headers_in.server.len == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent neither \":authority\" nor \"Host\" header"); + goto failed; + } + + if (r->headers_in.host) { + if (r->headers_in.host->value.len != r->headers_in.server.len + || ngx_memcmp(r->headers_in.host->value.data, + r->headers_in.server.data, + r->headers_in.server.len) + != 0) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent \":authority\" and \"Host\" headers " + "with different values"); + goto failed; + } + } + + if (r->headers_in.content_length) { + r->headers_in.content_length_n = + ngx_atoof(r->headers_in.content_length->value.data, + r->headers_in.content_length->value.len); + + if (r->headers_in.content_length_n == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent invalid \"Content-Length\" header"); + goto failed; + } + + } else { + b = r->header_in; + n = b->last - b->pos; + + if (n == 0) { + n = c->recv(c, b->start, b->end - b->start); + + if (n == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (n > 0) { + b->pos = b->start; + b->last = b->start + n; + } + } + + if (n != 0) { + r->headers_in.chunked = 1; + } + } + + if (r->method == NGX_HTTP_CONNECT) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent CONNECT method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + + if (r->method == NGX_HTTP_TRACE) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent TRACE method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + + return NGX_OK; + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value) +{ + ngx_str_t *val; + ngx_array_t *cookies; + + cookies = r->v3_parse->cookies; + + if (cookies == NULL) { + cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); + if (cookies == NULL) { + return NGX_ERROR; + } + + r->v3_parse->cookies = cookies; + } + + val = ngx_array_push(cookies); + if (val == NULL) { + return NGX_ERROR; + } + + *val = *value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_construct_cookie_header(ngx_http_request_t *r) +{ + u_char *buf, *p, *end; + size_t len; + ngx_str_t *vals; + ngx_uint_t i; + ngx_array_t *cookies; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + cookies = r->v3_parse->cookies; + + if (cookies == NULL) { + return NGX_OK; + } + + vals = cookies->elts; + + i = 0; + len = 0; + + do { + len += vals[i].len + 2; + } while (++i != cookies->nelts); + + len -= 2; + + buf = ngx_pnalloc(r->pool, len + 1); + if (buf == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + p = buf; + end = buf + len; + + for (i = 0; /* void */ ; i++) { + + p = ngx_cpymem(p, vals[i].data, vals[i].len); + + if (p == end) { + *p = '\0'; + break; + } + + *p++ = ';'; *p++ = ' '; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( + ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e'); + + h->key.len = cookie.len; + h->key.data = cookie.data; + + h->value.len = len; + h->value.data = buf; + + h->lowcase_key = cookie.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_multi_header_lines() + */ + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_read_request_body(ngx_http_request_t *r) +{ + size_t preread; + ngx_int_t rc; + ngx_chain_t *cl, out; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + rb = r->request_body; + + preread = r->header_in->last - r->header_in->pos; + + if (preread) { + + /* there is the pre-read part of the request body */ + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 client request body preread %uz", preread); + + out.buf = r->header_in; + out.next = NULL; + cl = &out; + + } else { + cl = NULL; + } + + rc = ngx_http_v3_request_body_filter(r, cl); + if (rc != NGX_OK) { + return rc; + } + + if (rb->rest == 0 && rb->last_saved) { + /* the whole request body was pre-read */ + r->request_body_no_buffering = 0; + rb->post_handler(r); + return NGX_OK; + } + + if (rb->rest < 0) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "negative request body rest"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + rb->buf = ngx_create_temp_buf(r->pool, clcf->client_body_buffer_size); + if (rb->buf == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + r->read_event_handler = ngx_http_v3_read_client_request_body_handler; + r->write_event_handler = ngx_http_request_empty_handler; + + return ngx_http_v3_do_read_client_request_body(r); +} + + +static void +ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + + if (r->connection->read->timedout) { + r->connection->timedout = 1; + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + rc = ngx_http_v3_do_read_client_request_body(r); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + ngx_http_finalize_request(r, rc); + } +} + + +ngx_int_t +ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r) +{ + ngx_int_t rc; + + if (r->connection->read->timedout) { + r->connection->timedout = 1; + return NGX_HTTP_REQUEST_TIME_OUT; + } + + rc = ngx_http_v3_do_read_client_request_body(r); + + if (rc == NGX_OK) { + r->reading_body = 0; + } + + return rc; +} + + +static ngx_int_t +ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) +{ + off_t rest; + size_t size; + ssize_t n; + ngx_int_t rc; + ngx_uint_t flush; + ngx_chain_t out; + ngx_connection_t *c; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + c = r->connection; + rb = r->request_body; + flush = 1; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 read client request body"); + + for ( ;; ) { + for ( ;; ) { + if (rb->rest == 0) { + break; + } + + if (rb->buf->last == rb->buf->end) { + + /* update chains */ + + rc = ngx_http_v3_request_body_filter(r, NULL); + + if (rc != NGX_OK) { + return rc; + } + + if (rb->busy != NULL) { + if (r->request_body_no_buffering) { + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + + if (rb->filter_need_buffering) { + clcf = ngx_http_get_module_loc_conf(r, + ngx_http_core_module); + ngx_add_timer(c->read, clcf->client_body_timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "busy buffers after request body flush"); + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + flush = 0; + rb->buf->pos = rb->buf->start; + rb->buf->last = rb->buf->start; + } + + size = rb->buf->end - rb->buf->last; + rest = rb->rest - (rb->buf->last - rb->buf->pos); + + if ((off_t) size > rest) { + size = (size_t) rest; + } + + if (size == 0) { + break; + } + + n = c->recv(c, rb->buf->last, size); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client request body recv %z", n); + + if (n == NGX_AGAIN) { + break; + } + + if (n == 0) { + rb->buf->last_buf = 1; + } + + if (n == NGX_ERROR) { + c->error = 1; + return NGX_HTTP_BAD_REQUEST; + } + + rb->buf->last += n; + + /* pass buffer to request body filter chain */ + + flush = 0; + out.buf = rb->buf; + out.next = NULL; + + rc = ngx_http_v3_request_body_filter(r, &out); + + if (rc != NGX_OK) { + return rc; + } + + if (rb->rest == 0) { + break; + } + + if (rb->buf->last < rb->buf->end) { + break; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client request body rest %O", rb->rest); + + if (flush) { + rc = ngx_http_v3_request_body_filter(r, NULL); + + if (rc != NGX_OK) { + return rc; + } + } + + if (rb->rest == 0 && rb->last_saved) { + break; + } + + if (!c->read->ready || rb->rest == 0) { + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(c->read, clcf->client_body_timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (!r->request_body_no_buffering) { + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + off_t max; + size_t size; + u_char *p; + ngx_int_t rc; + ngx_buf_t *b; + ngx_uint_t last; + ngx_chain_t *cl, *out, *tl, **ll; + ngx_http_v3_session_t *h3c; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + ngx_http_v3_parse_data_t *st; + + rb = r->request_body; + st = &r->v3_parse->body; + + h3c = ngx_http_v3_get_session(r->connection); + + if (rb->rest == -1) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request body filter"); + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + rb->rest = cscf->large_client_header_buffers.size; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + max = r->headers_in.content_length_n; + + if (max == -1 && clcf->client_max_body_size) { + max = clcf->client_max_body_size; + } + + out = NULL; + ll = &out; + last = 0; + + for (cl = in; cl; cl = cl->next) { + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, + "http3 body buf " + "t:%d f:%d %p, pos %p, size: %z file: %O, size: %O", + cl->buf->temporary, cl->buf->in_file, + cl->buf->start, cl->buf->pos, + cl->buf->last - cl->buf->pos, + cl->buf->file_pos, + cl->buf->file_last - cl->buf->file_pos); + + if (cl->buf->last_buf) { + last = 1; + } + + b = NULL; + + while (cl->buf->pos < cl->buf->last) { + + if (st->length == 0) { + p = cl->buf->pos; + + rc = ngx_http_v3_parse_data(r->connection, st, cl->buf); + + r->request_length += cl->buf->pos - p; + h3c->total_bytes += cl->buf->pos - p; + + if (ngx_http_v3_check_flood(r->connection) != NGX_OK) { + return NGX_HTTP_CLOSE; + } + + if (rc == NGX_AGAIN) { + continue; + } + + if (rc == NGX_DONE) { + last = 1; + goto done; + } + + if (rc > 0) { + ngx_quic_reset_stream(r->connection, rc); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid body"); + return NGX_HTTP_BAD_REQUEST; + } + + if (rc == NGX_ERROR) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* rc == NGX_OK */ + + if (max != -1 && (uint64_t) (max - rb->received) < st->length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send too large " + "body: %O+%ui bytes", + rb->received, st->length); + + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } + + continue; + } + + if (b + && st->length <= 128 + && (uint64_t) (cl->buf->last - cl->buf->pos) >= st->length) + { + rb->received += st->length; + r->request_length += st->length; + h3c->total_bytes += st->length; + h3c->payload_bytes += st->length; + + if (st->length < 8) { + + while (st->length) { + *b->last++ = *cl->buf->pos++; + st->length--; + } + + } else { + ngx_memmove(b->last, cl->buf->pos, st->length); + b->last += st->length; + cl->buf->pos += st->length; + st->length = 0; + } + + continue; + } + + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->temporary = 1; + b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body; + b->start = cl->buf->pos; + b->pos = cl->buf->pos; + b->last = cl->buf->last; + b->end = cl->buf->end; + b->flush = r->request_body_no_buffering; + + *ll = tl; + ll = &tl->next; + + size = cl->buf->last - cl->buf->pos; + + if (size > st->length) { + cl->buf->pos += (size_t) st->length; + rb->received += st->length; + r->request_length += st->length; + h3c->total_bytes += st->length; + h3c->payload_bytes += st->length; + st->length = 0; + + } else { + st->length -= size; + rb->received += size; + r->request_length += size; + h3c->total_bytes += size; + h3c->payload_bytes += size; + cl->buf->pos = cl->buf->last; + } + + b->last = cl->buf->pos; + } + } + +done: + + if (last) { + + if (st->length > 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + r->connection->error = 1; + return NGX_HTTP_BAD_REQUEST; + } + + if (r->headers_in.content_length_n == -1) { + r->headers_in.content_length_n = rb->received; + + } else if (r->headers_in.content_length_n != rb->received) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent less body data than expected: " + "%O out of %O bytes of request body received", + rb->received, r->headers_in.content_length_n); + return NGX_HTTP_BAD_REQUEST; + } + + rb->rest = 0; + + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->last_buf = 1; + + *ll = tl; + + } else { + + /* set rb->rest, amount of data we want to see next time */ + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + rb->rest = (off_t) cscf->large_client_header_buffers.size; + } + + rc = ngx_http_top_request_body_filter(r, out); + + ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out, + (ngx_buf_tag_t) &ngx_http_read_client_request_body); + + return rc; +} diff --git a/src/http/v3/ngx_http_v3_table.c b/src/http/v3/ngx_http_v3_table.c new file mode 100644 index 0000000..428e732 --- /dev/null +++ b/src/http/v3/ngx_http_v3_table.c @@ -0,0 +1,715 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +#define ngx_http_v3_table_entry_size(n, v) ((n)->len + (v)->len + 32) + + +static ngx_int_t ngx_http_v3_evict(ngx_connection_t *c, size_t target); +static void ngx_http_v3_unblock(void *data); +static ngx_int_t ngx_http_v3_new_entry(ngx_connection_t *c); + + +typedef struct { + ngx_queue_t queue; + ngx_connection_t *connection; + ngx_uint_t *nblocked; +} ngx_http_v3_block_t; + + +static ngx_http_v3_field_t ngx_http_v3_static_table[] = { + + { ngx_string(":authority"), ngx_string("") }, + { ngx_string(":path"), ngx_string("/") }, + { ngx_string("age"), ngx_string("0") }, + { ngx_string("content-disposition"), ngx_string("") }, + { ngx_string("content-length"), ngx_string("0") }, + { ngx_string("cookie"), ngx_string("") }, + { ngx_string("date"), ngx_string("") }, + { ngx_string("etag"), ngx_string("") }, + { ngx_string("if-modified-since"), ngx_string("") }, + { ngx_string("if-none-match"), ngx_string("") }, + { ngx_string("last-modified"), ngx_string("") }, + { ngx_string("link"), ngx_string("") }, + { ngx_string("location"), ngx_string("") }, + { ngx_string("referer"), ngx_string("") }, + { ngx_string("set-cookie"), ngx_string("") }, + { ngx_string(":method"), ngx_string("CONNECT") }, + { ngx_string(":method"), ngx_string("DELETE") }, + { ngx_string(":method"), ngx_string("GET") }, + { ngx_string(":method"), ngx_string("HEAD") }, + { ngx_string(":method"), ngx_string("OPTIONS") }, + { ngx_string(":method"), ngx_string("POST") }, + { ngx_string(":method"), ngx_string("PUT") }, + { ngx_string(":scheme"), ngx_string("http") }, + { ngx_string(":scheme"), ngx_string("https") }, + { ngx_string(":status"), ngx_string("103") }, + { ngx_string(":status"), ngx_string("200") }, + { ngx_string(":status"), ngx_string("304") }, + { ngx_string(":status"), ngx_string("404") }, + { ngx_string(":status"), ngx_string("503") }, + { ngx_string("accept"), ngx_string("*/*") }, + { ngx_string("accept"), + ngx_string("application/dns-message") }, + { ngx_string("accept-encoding"), ngx_string("gzip, deflate, br") }, + { ngx_string("accept-ranges"), ngx_string("bytes") }, + { ngx_string("access-control-allow-headers"), + ngx_string("cache-control") }, + { ngx_string("access-control-allow-headers"), + ngx_string("content-type") }, + { ngx_string("access-control-allow-origin"), + ngx_string("*") }, + { ngx_string("cache-control"), ngx_string("max-age=0") }, + { ngx_string("cache-control"), ngx_string("max-age=2592000") }, + { ngx_string("cache-control"), ngx_string("max-age=604800") }, + { ngx_string("cache-control"), ngx_string("no-cache") }, + { ngx_string("cache-control"), ngx_string("no-store") }, + { ngx_string("cache-control"), + ngx_string("public, max-age=31536000") }, + { ngx_string("content-encoding"), ngx_string("br") }, + { ngx_string("content-encoding"), ngx_string("gzip") }, + { ngx_string("content-type"), + ngx_string("application/dns-message") }, + { ngx_string("content-type"), + ngx_string("application/javascript") }, + { ngx_string("content-type"), ngx_string("application/json") }, + { ngx_string("content-type"), + ngx_string("application/x-www-form-urlencoded") }, + { ngx_string("content-type"), ngx_string("image/gif") }, + { ngx_string("content-type"), ngx_string("image/jpeg") }, + { ngx_string("content-type"), ngx_string("image/png") }, + { ngx_string("content-type"), ngx_string("text/css") }, + { ngx_string("content-type"), + ngx_string("text/html;charset=utf-8") }, + { ngx_string("content-type"), ngx_string("text/plain") }, + { ngx_string("content-type"), + ngx_string("text/plain;charset=utf-8") }, + { ngx_string("range"), ngx_string("bytes=0-") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000;includesubdomains") }, + { ngx_string("strict-transport-security"), + ngx_string("max-age=31536000;includesubdomains;preload") }, + { ngx_string("vary"), ngx_string("accept-encoding") }, + { ngx_string("vary"), ngx_string("origin") }, + { ngx_string("x-content-type-options"), + ngx_string("nosniff") }, + { ngx_string("x-xss-protection"), ngx_string("1;mode=block") }, + { ngx_string(":status"), ngx_string("100") }, + { ngx_string(":status"), ngx_string("204") }, + { ngx_string(":status"), ngx_string("206") }, + { ngx_string(":status"), ngx_string("302") }, + { ngx_string(":status"), ngx_string("400") }, + { ngx_string(":status"), ngx_string("403") }, + { ngx_string(":status"), ngx_string("421") }, + { ngx_string(":status"), ngx_string("425") }, + { ngx_string(":status"), ngx_string("500") }, + { ngx_string("accept-language"), ngx_string("") }, + { ngx_string("access-control-allow-credentials"), + ngx_string("FALSE") }, + { ngx_string("access-control-allow-credentials"), + ngx_string("TRUE") }, + { ngx_string("access-control-allow-headers"), + ngx_string("*") }, + { ngx_string("access-control-allow-methods"), + ngx_string("get") }, + { ngx_string("access-control-allow-methods"), + ngx_string("get, post, options") }, + { ngx_string("access-control-allow-methods"), + ngx_string("options") }, + { ngx_string("access-control-expose-headers"), + ngx_string("content-length") }, + { ngx_string("access-control-request-headers"), + ngx_string("content-type") }, + { ngx_string("access-control-request-method"), + ngx_string("get") }, + { ngx_string("access-control-request-method"), + ngx_string("post") }, + { ngx_string("alt-svc"), ngx_string("clear") }, + { ngx_string("authorization"), ngx_string("") }, + { ngx_string("content-security-policy"), + ngx_string("script-src 'none';object-src 'none';base-uri 'none'") }, + { ngx_string("early-data"), ngx_string("1") }, + { ngx_string("expect-ct"), ngx_string("") }, + { ngx_string("forwarded"), ngx_string("") }, + { ngx_string("if-range"), ngx_string("") }, + { ngx_string("origin"), ngx_string("") }, + { ngx_string("purpose"), ngx_string("prefetch") }, + { ngx_string("server"), ngx_string("") }, + { ngx_string("timing-allow-origin"), ngx_string("*") }, + { ngx_string("upgrade-insecure-requests"), + ngx_string("1") }, + { ngx_string("user-agent"), ngx_string("") }, + { ngx_string("x-forwarded-for"), ngx_string("") }, + { ngx_string("x-frame-options"), ngx_string("deny") }, + { ngx_string("x-frame-options"), ngx_string("sameorigin") } +}; + + +ngx_int_t +ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value) +{ + ngx_str_t name; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + if (dynamic) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ref insert dynamic[%ui] \"%V\"", index, value); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->base + dt->nelts <= index) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + index = dt->base + dt->nelts - 1 - index; + + if (ngx_http_v3_lookup(c, index, &name, NULL) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + } else { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ref insert static[%ui] \"%V\"", index, value); + + if (ngx_http_v3_lookup_static(c, index, &name, NULL) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + } + + return ngx_http_v3_insert(c, &name, value); +} + + +ngx_int_t +ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, ngx_str_t *value) +{ + u_char *p; + size_t size; + ngx_http_v3_field_t *field; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + size = ngx_http_v3_table_entry_size(name, value); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (size > dt->capacity) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "not enough dynamic table capacity"); + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 insert [%ui] \"%V\":\"%V\", size:%uz", + dt->base + dt->nelts, name, value, size); + + p = ngx_alloc(sizeof(ngx_http_v3_field_t) + name->len + value->len, + c->log); + if (p == NULL) { + return NGX_ERROR; + } + + field = (ngx_http_v3_field_t *) p; + + field->name.data = p + sizeof(ngx_http_v3_field_t); + field->name.len = name->len; + field->value.data = ngx_cpymem(field->name.data, name->data, name->len); + field->value.len = value->len; + ngx_memcpy(field->value.data, value->data, value->len); + + dt->elts[dt->nelts++] = field; + dt->size += size; + + dt->insert_count++; + + if (ngx_http_v3_evict(c, dt->capacity) != NGX_OK) { + return NGX_ERROR; + } + + ngx_post_event(&dt->send_insert_count, &ngx_posted_events); + + if (ngx_http_v3_new_entry(c) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +void +ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + c = ev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 inc insert count handler"); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->insert_count > dt->ack_insert_count) { + if (ngx_http_v3_send_inc_insert_count(c, + dt->insert_count - dt->ack_insert_count) + != NGX_OK) + { + return; + } + + dt->ack_insert_count = dt->insert_count; + } +} + + +ngx_int_t +ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity) +{ + ngx_uint_t max, prev_max; + ngx_http_v3_field_t **elts; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_dynamic_table_t *dt; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 set capacity %ui", capacity); + + h3c = ngx_http_v3_get_session(c); + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (capacity > h3scf->max_table_capacity) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded http3_max_table_capacity limit"); + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + if (ngx_http_v3_evict(c, capacity) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + dt = &h3c->table; + max = capacity / 32; + prev_max = dt->capacity / 32; + + if (max > prev_max) { + elts = ngx_alloc((max + 1) * sizeof(void *), c->log); + if (elts == NULL) { + return NGX_ERROR; + } + + if (dt->elts) { + ngx_memcpy(elts, dt->elts, dt->nelts * sizeof(void *)); + ngx_free(dt->elts); + } + + dt->elts = elts; + } + + dt->capacity = capacity; + + return NGX_OK; +} + + +void +ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c) +{ + ngx_uint_t n; + ngx_http_v3_dynamic_table_t *dt; + + dt = &h3c->table; + + if (dt->elts == NULL) { + return; + } + + for (n = 0; n < dt->nelts; n++) { + ngx_free(dt->elts[n]); + } + + ngx_free(dt->elts); +} + + +static ngx_int_t +ngx_http_v3_evict(ngx_connection_t *c, size_t target) +{ + size_t size; + ngx_uint_t n; + ngx_http_v3_field_t *field; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + n = 0; + + while (dt->size > target) { + field = dt->elts[n++]; + size = ngx_http_v3_table_entry_size(&field->name, &field->value); + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 evict [%ui] \"%V\":\"%V\" size:%uz", + dt->base, &field->name, &field->value, size); + + ngx_free(field); + dt->size -= size; + } + + if (n) { + dt->nelts -= n; + dt->base += n; + ngx_memmove(dt->elts, &dt->elts[n], dt->nelts * sizeof(void *)); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index) +{ + ngx_str_t name, value; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 duplicate %ui", index); + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->base + dt->nelts <= index) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + index = dt->base + dt->nelts - 1 - index; + + if (ngx_http_v3_lookup(c, index, &name, &value) != NGX_OK) { + return NGX_HTTP_V3_ERR_ENCODER_STREAM_ERROR; + } + + return ngx_http_v3_insert(c, &name, &value); +} + + +ngx_int_t +ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 ack section %ui", stream_id); + + /* we do not use dynamic tables */ + + return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; +} + + +ngx_int_t +ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 increment insert count %ui", inc); + + /* we do not use dynamic tables */ + + return NGX_HTTP_V3_ERR_DECODER_STREAM_ERROR; +} + + +ngx_int_t +ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value) +{ + ngx_uint_t nelts; + ngx_http_v3_field_t *field; + + nelts = sizeof(ngx_http_v3_static_table) + / sizeof(ngx_http_v3_static_table[0]); + + if (index >= nelts) { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 static[%ui] lookup out of bounds: %ui", + index, nelts); + return NGX_ERROR; + } + + field = &ngx_http_v3_static_table[index]; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 static[%ui] lookup \"%V\":\"%V\"", + index, &field->name, &field->value); + + if (name) { + *name = field->name; + } + + if (value) { + *value = field->value; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, ngx_str_t *name, + ngx_str_t *value) +{ + ngx_http_v3_field_t *field; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (index < dt->base || index - dt->base >= dt->nelts) { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 dynamic[%ui] lookup out of bounds: [%ui,%ui]", + index, dt->base, dt->base + dt->nelts); + return NGX_ERROR; + } + + field = dt->elts[index - dt->base]; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 dynamic[%ui] lookup \"%V\":\"%V\"", + index, &field->name, &field->value); + + if (name) { + *name = field->name; + } + + if (value) { + *value = field->value; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_decode_insert_count(ngx_connection_t *c, ngx_uint_t *insert_count) +{ + ngx_uint_t max_entries, full_range, max_value, + max_wrapped, req_insert_count; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + /* QPACK 4.5.1.1. Required Insert Count */ + + if (*insert_count == 0) { + return NGX_OK; + } + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + max_entries = h3scf->max_table_capacity / 32; + full_range = 2 * max_entries; + + if (*insert_count > full_range) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + max_value = dt->base + dt->nelts + max_entries; + max_wrapped = (max_value / full_range) * full_range; + req_insert_count = max_wrapped + *insert_count - 1; + + if (req_insert_count > max_value) { + if (req_insert_count <= full_range) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + req_insert_count -= full_range; + } + + if (req_insert_count == 0) { + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 decode insert_count %ui -> %ui", + *insert_count, req_insert_count); + + *insert_count = req_insert_count; + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_check_insert_count(ngx_connection_t *c, ngx_uint_t insert_count) +{ + size_t n; + ngx_pool_cleanup_t *cln; + ngx_http_v3_block_t *block; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + n = dt->base + dt->nelts; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 check insert count req:%ui, have:%ui", + insert_count, n); + + if (n >= insert_count) { + return NGX_OK; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 block stream"); + + block = NULL; + + for (cln = c->pool->cleanup; cln; cln = cln->next) { + if (cln->handler == ngx_http_v3_unblock) { + block = cln->data; + break; + } + } + + if (block == NULL) { + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_http_v3_block_t)); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_http_v3_unblock; + + block = cln->data; + block->queue.prev = NULL; + block->connection = c; + block->nblocked = &h3c->nblocked; + } + + if (block->queue.prev == NULL) { + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (h3c->nblocked == h3scf->max_blocked_streams) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client exceeded http3_max_blocked_streams limit"); + + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED, + "too many blocked streams"); + return NGX_HTTP_V3_ERR_DECOMPRESSION_FAILED; + } + + h3c->nblocked++; + ngx_queue_insert_tail(&h3c->blocked, &block->queue); + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 blocked:%ui", h3c->nblocked); + + return NGX_BUSY; +} + + +void +ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count) +{ + ngx_http_v3_session_t *h3c; + ngx_http_v3_dynamic_table_t *dt; + + h3c = ngx_http_v3_get_session(c); + dt = &h3c->table; + + if (dt->ack_insert_count < insert_count) { + dt->ack_insert_count = insert_count; + } +} + + +static void +ngx_http_v3_unblock(void *data) +{ + ngx_http_v3_block_t *block = data; + + if (block->queue.prev) { + ngx_queue_remove(&block->queue); + block->queue.prev = NULL; + (*block->nblocked)--; + } +} + + +static ngx_int_t +ngx_http_v3_new_entry(ngx_connection_t *c) +{ + ngx_queue_t *q; + ngx_connection_t *bc; + ngx_http_v3_block_t *block; + ngx_http_v3_session_t *h3c; + + h3c = ngx_http_v3_get_session(c); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 new dynamic entry, blocked:%ui", h3c->nblocked); + + while (!ngx_queue_empty(&h3c->blocked)) { + q = ngx_queue_head(&h3c->blocked); + block = (ngx_http_v3_block_t *) q; + bc = block->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, bc->log, 0, "http3 unblock stream"); + + ngx_http_v3_unblock(block); + ngx_post_event(bc->read, &ngx_posted_events); + } + + return NGX_OK; +} + + +ngx_int_t +ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, uint64_t value) +{ + switch (id) { + + case NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param QPACK_MAX_TABLE_CAPACITY:%uL", value); + break; + + case NGX_HTTP_V3_PARAM_MAX_FIELD_SECTION_SIZE: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param SETTINGS_MAX_FIELD_SECTION_SIZE:%uL", + value); + break; + + case NGX_HTTP_V3_PARAM_BLOCKED_STREAMS: + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param QPACK_BLOCKED_STREAMS:%uL", value); + break; + + default: + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 param #%uL:%uL", id, value); + } + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3_table.h b/src/http/v3/ngx_http_v3_table.h new file mode 100644 index 0000000..1c2fb17 --- /dev/null +++ b/src/http/v3/ngx_http_v3_table.h @@ -0,0 +1,58 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_TABLE_H_INCLUDED_ +#define _NGX_HTTP_V3_TABLE_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +typedef struct { + ngx_str_t name; + ngx_str_t value; +} ngx_http_v3_field_t; + + +typedef struct { + ngx_http_v3_field_t **elts; + ngx_uint_t nelts; + ngx_uint_t base; + size_t size; + size_t capacity; + uint64_t insert_count; + uint64_t ack_insert_count; + ngx_event_t send_insert_count; +} ngx_http_v3_dynamic_table_t; + + +void ngx_http_v3_inc_insert_count_handler(ngx_event_t *ev); +void ngx_http_v3_cleanup_table(ngx_http_v3_session_t *h3c); +ngx_int_t ngx_http_v3_ref_insert(ngx_connection_t *c, ngx_uint_t dynamic, + ngx_uint_t index, ngx_str_t *value); +ngx_int_t ngx_http_v3_insert(ngx_connection_t *c, ngx_str_t *name, + ngx_str_t *value); +ngx_int_t ngx_http_v3_set_capacity(ngx_connection_t *c, ngx_uint_t capacity); +ngx_int_t ngx_http_v3_duplicate(ngx_connection_t *c, ngx_uint_t index); +ngx_int_t ngx_http_v3_ack_section(ngx_connection_t *c, ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc); +ngx_int_t ngx_http_v3_lookup_static(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v3_lookup(ngx_connection_t *c, ngx_uint_t index, + ngx_str_t *name, ngx_str_t *value); +ngx_int_t ngx_http_v3_decode_insert_count(ngx_connection_t *c, + ngx_uint_t *insert_count); +ngx_int_t ngx_http_v3_check_insert_count(ngx_connection_t *c, + ngx_uint_t insert_count); +void ngx_http_v3_ack_insert_count(ngx_connection_t *c, uint64_t insert_count); +ngx_int_t ngx_http_v3_set_param(ngx_connection_t *c, uint64_t id, + uint64_t value); + + +#endif /* _NGX_HTTP_V3_TABLE_H_INCLUDED_ */ diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c new file mode 100644 index 0000000..302064b --- /dev/null +++ b/src/http/v3/ngx_http_v3_uni.c @@ -0,0 +1,622 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +typedef struct { + ngx_http_v3_parse_uni_t parse; + ngx_int_t index; +} ngx_http_v3_uni_stream_t; + + +static void ngx_http_v3_close_uni_stream(ngx_connection_t *c); +static void ngx_http_v3_uni_read_handler(ngx_event_t *rev); +static void ngx_http_v3_uni_dummy_read_handler(ngx_event_t *wev); +static void ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev); + + +void +ngx_http_v3_init_uni_stream(ngx_connection_t *c) +{ + uint64_t n; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + h3c = ngx_http_v3_get_session(c); + if (h3c->hq) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "uni stream in hq mode"); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init uni stream"); + + n = c->quic->id >> 2; + + if (n >= NGX_HTTP_V3_MAX_UNI_STREAMS) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "reached maximum number of uni streams"); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); + return; + } + + ngx_quic_cancelable_stream(c); + + us = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_uni_stream_t)); + if (us == NULL) { + ngx_http_v3_finalize_connection(c, + NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "memory allocation error"); + c->data = NULL; + ngx_http_v3_close_uni_stream(c); + return; + } + + us->index = -1; + + c->data = us; + + c->read->handler = ngx_http_v3_uni_read_handler; + c->write->handler = ngx_http_v3_uni_dummy_write_handler; + + ngx_http_v3_uni_read_handler(c->read); +} + + +static void +ngx_http_v3_close_uni_stream(ngx_connection_t *c) +{ + ngx_pool_t *pool; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 close stream"); + + us = c->data; + + if (us && us->index >= 0) { + h3c = ngx_http_v3_get_session(c); + h3c->known_streams[us->index] = NULL; + } + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); +} + + +ngx_int_t +ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type) +{ + ngx_int_t index; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + h3c = ngx_http_v3_get_session(c); + + switch (type) { + + case NGX_HTTP_V3_STREAM_ENCODER: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 encoder stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_ENCODER; + break; + + case NGX_HTTP_V3_STREAM_DECODER: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 decoder stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_DECODER; + break; + + case NGX_HTTP_V3_STREAM_CONTROL: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 control stream"); + index = NGX_HTTP_V3_STREAM_CLIENT_CONTROL; + + break; + + default: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 stream 0x%02xL", type); + + if (h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL + || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_DECODER] == NULL + || h3c->known_streams[NGX_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream"); + return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; + } + + index = -1; + } + + if (index >= 0) { + if (h3c->known_streams[index]) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "stream exists"); + return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; + } + + h3c->known_streams[index] = c; + + us = c->data; + us->index = index; + } + + return NGX_OK; +} + + +static void +ngx_http_v3_uni_read_handler(ngx_event_t *rev) +{ + u_char buf[128]; + ssize_t n; + ngx_buf_t b; + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + c = rev->data; + us = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read handler"); + + if (c->close) { + ngx_http_v3_close_uni_stream(c); + return; + } + + ngx_memzero(&b, sizeof(ngx_buf_t)); + + while (rev->ready) { + + n = c->recv(c, buf, sizeof(buf)); + + if (n == NGX_ERROR) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; + goto failed; + } + + if (n == 0) { + if (us->index >= 0) { + rc = NGX_HTTP_V3_ERR_CLOSED_CRITICAL_STREAM; + goto failed; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read eof"); + ngx_http_v3_close_uni_stream(c); + return; + } + + if (n == NGX_AGAIN) { + break; + } + + b.pos = buf; + b.last = buf + n; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (ngx_http_v3_check_flood(c) != NGX_OK) { + ngx_http_v3_close_uni_stream(c); + return; + } + + rc = ngx_http_v3_parse_uni(c, &us->parse, &b); + + if (rc == NGX_DONE) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 read done"); + ngx_http_v3_close_uni_stream(c); + return; + } + + if (rc > 0) { + goto failed; + } + + if (rc != NGX_AGAIN) { + rc = NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR; + goto failed; + } + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + rc = NGX_HTTP_V3_ERR_INTERNAL_ERROR; + goto failed; + } + + return; + +failed: + + ngx_http_v3_finalize_connection(c, rc, "stream error"); + ngx_http_v3_close_uni_stream(c); +} + + +static void +ngx_http_v3_uni_dummy_read_handler(ngx_event_t *rev) +{ + u_char ch; + ngx_connection_t *c; + + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy read handler"); + + if (c->close) { + ngx_http_v3_close_uni_stream(c); + return; + } + + if (rev->ready) { + if (c->recv(c, &ch, 1) != 0) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, NULL); + ngx_http_v3_close_uni_stream(c); + return; + } + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + NULL); + ngx_http_v3_close_uni_stream(c); + } +} + + +static void +ngx_http_v3_uni_dummy_write_handler(ngx_event_t *wev) +{ + ngx_connection_t *c; + + c = wev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 dummy write handler"); + + if (ngx_handle_write_event(wev, 0) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + NULL); + ngx_http_v3_close_uni_stream(c); + } +} + + +ngx_connection_t * +ngx_http_v3_get_uni_stream(ngx_connection_t *c, ngx_uint_t type) +{ + u_char buf[NGX_HTTP_V3_VARLEN_INT_LEN]; + size_t n; + ngx_int_t index; + ngx_connection_t *sc; + ngx_http_v3_session_t *h3c; + ngx_http_v3_uni_stream_t *us; + + switch (type) { + case NGX_HTTP_V3_STREAM_ENCODER: + index = NGX_HTTP_V3_STREAM_SERVER_ENCODER; + break; + case NGX_HTTP_V3_STREAM_DECODER: + index = NGX_HTTP_V3_STREAM_SERVER_DECODER; + break; + case NGX_HTTP_V3_STREAM_CONTROL: + index = NGX_HTTP_V3_STREAM_SERVER_CONTROL; + break; + default: + index = -1; + } + + h3c = ngx_http_v3_get_session(c); + + if (index >= 0) { + if (h3c->known_streams[index]) { + return h3c->known_streams[index]; + } + } + + sc = ngx_quic_open_stream(c, 0); + if (sc == NULL) { + goto failed; + } + + ngx_quic_cancelable_stream(sc); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 create uni stream, type:%ui", type); + + us = ngx_pcalloc(sc->pool, sizeof(ngx_http_v3_uni_stream_t)); + if (us == NULL) { + goto failed; + } + + us->index = index; + + sc->data = us; + + sc->read->handler = ngx_http_v3_uni_dummy_read_handler; + sc->write->handler = ngx_http_v3_uni_dummy_write_handler; + + if (index >= 0) { + h3c->known_streams[index] = sc; + } + + n = (u_char *) ngx_http_v3_encode_varlen_int(buf, type) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (sc->send(sc, buf, n) != (ssize_t) n) { + goto failed; + } + + ngx_post_event(sc->read, &ngx_posted_events); + + return sc; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, + "failed to create server stream"); + if (sc) { + ngx_http_v3_close_uni_stream(sc); + } + + return NULL; +} + + +ngx_int_t +ngx_http_v3_send_settings(ngx_connection_t *c) +{ + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 6]; + size_t n; + ngx_connection_t *cc; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send settings"); + + cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); + if (cc == NULL) { + return NGX_ERROR; + } + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + n = ngx_http_v3_encode_varlen_int(NULL, + NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); + n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_table_capacity); + n += ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); + n += ngx_http_v3_encode_varlen_int(NULL, h3scf->max_blocked_streams); + + p = (u_char *) ngx_http_v3_encode_varlen_int(buf, + NGX_HTTP_V3_FRAME_SETTINGS); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, + NGX_HTTP_V3_PARAM_MAX_TABLE_CAPACITY); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_table_capacity); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, + NGX_HTTP_V3_PARAM_BLOCKED_STREAMS); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, h3scf->max_blocked_streams); + n = p - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (cc->send(cc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send settings"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send settings"); + ngx_http_v3_close_uni_stream(cc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id) +{ + u_char *p, buf[NGX_HTTP_V3_VARLEN_INT_LEN * 3]; + size_t n; + ngx_connection_t *cc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 send goaway %uL", id); + + cc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_CONTROL); + if (cc == NULL) { + return NGX_ERROR; + } + + n = ngx_http_v3_encode_varlen_int(NULL, id); + p = (u_char *) ngx_http_v3_encode_varlen_int(buf, NGX_HTTP_V3_FRAME_GOAWAY); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, n); + p = (u_char *) ngx_http_v3_encode_varlen_int(p, id); + n = p - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (cc->send(cc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send goaway"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send goaway"); + ngx_http_v3_close_uni_stream(cc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_ack_section(ngx_connection_t *c, ngx_uint_t stream_id) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 send section acknowledgement %ui", stream_id); + + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0x80; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 7) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "failed to send section acknowledgement"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send section acknowledgement"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 send stream cancellation %ui", stream_id); + + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0x40; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, stream_id, 6) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to send stream cancellation"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send stream cancellation"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, ngx_uint_t inc) +{ + u_char buf[NGX_HTTP_V3_PREFIX_INT_LEN]; + size_t n; + ngx_connection_t *dc; + ngx_http_v3_session_t *h3c; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 send insert count increment %ui", inc); + + dc = ngx_http_v3_get_uni_stream(c, NGX_HTTP_V3_STREAM_DECODER); + if (dc == NULL) { + return NGX_ERROR; + } + + buf[0] = 0; + n = (u_char *) ngx_http_v3_encode_prefix_int(buf, inc, 6) - buf; + + h3c = ngx_http_v3_get_session(c); + h3c->total_bytes += n; + + if (dc->send(dc, buf, n) != (ssize_t) n) { + goto failed; + } + + return NGX_OK; + +failed: + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "failed to send insert count increment"); + + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "failed to send insert count increment"); + ngx_http_v3_close_uni_stream(dc); + + return NGX_ERROR; +} + + +ngx_int_t +ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id) +{ + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 cancel stream %ui", stream_id); + + /* we do not use dynamic tables */ + + return NGX_OK; +} diff --git a/src/http/v3/ngx_http_v3_uni.h b/src/http/v3/ngx_http_v3_uni.h new file mode 100644 index 0000000..2805894 --- /dev/null +++ b/src/http/v3/ngx_http_v3_uni.h @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_HTTP_V3_UNI_H_INCLUDED_ +#define _NGX_HTTP_V3_UNI_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_http.h> + + +void ngx_http_v3_init_uni_stream(ngx_connection_t *c); +ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type); + +ngx_int_t ngx_http_v3_cancel_stream(ngx_connection_t *c, ngx_uint_t stream_id); + +ngx_connection_t *ngx_http_v3_get_uni_stream(ngx_connection_t *c, + ngx_uint_t type); +ngx_int_t ngx_http_v3_send_settings(ngx_connection_t *c); +ngx_int_t ngx_http_v3_send_goaway(ngx_connection_t *c, uint64_t id); +ngx_int_t ngx_http_v3_send_ack_section(ngx_connection_t *c, + ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_send_cancel_stream(ngx_connection_t *c, + ngx_uint_t stream_id); +ngx_int_t ngx_http_v3_send_inc_insert_count(ngx_connection_t *c, + ngx_uint_t inc); + + +#endif /* _NGX_HTTP_V3_UNI_H_INCLUDED_ */ diff --git a/src/mail/ngx_mail_core_module.c b/src/mail/ngx_mail_core_module.c index 115671c..228a8d0 100644 --- a/src/mail/ngx_mail_core_module.c +++ b/src/mail/ngx_mail_core_module.c @@ -308,7 +308,7 @@ ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_str_t *value, size; ngx_url_t u; ngx_uint_t i, n, m; - ngx_mail_listen_t *ls, *als; + ngx_mail_listen_t *ls, *als, *nls; ngx_mail_module_t *module; ngx_mail_core_main_conf_t *cmcf; @@ -333,7 +333,7 @@ ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) cmcf = ngx_mail_conf_get_module_main_conf(cf, ngx_mail_core_module); - ls = ngx_array_push_n(&cmcf->listen, u.naddrs); + ls = ngx_array_push(&cmcf->listen); if (ls == NULL) { return NGX_CONF_ERROR; } @@ -441,7 +441,7 @@ ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "bind ipv6only is not supported " + "ipv6only is not supported " "on this platform"); return NGX_CONF_ERROR; #endif @@ -564,24 +564,44 @@ ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "the invalid \"%V\" parameter", &value[i]); + "invalid parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; } - als = cmcf->listen.elts; - for (n = 0; n < u.naddrs; n++) { - ls[n] = ls[0]; - ls[n].sockaddr = u.addrs[n].sockaddr; - ls[n].socklen = u.addrs[n].socklen; - ls[n].addr_text = u.addrs[n].name; - ls[n].wildcard = ngx_inet_wildcard(ls[n].sockaddr); + for (i = 0; i < n; i++) { + if (ngx_cmp_sockaddr(u.addrs[n].sockaddr, u.addrs[n].socklen, + u.addrs[i].sockaddr, u.addrs[i].socklen, 1) + == NGX_OK) + { + goto next; + } + } + + if (n != 0) { + nls = ngx_array_push(&cmcf->listen); + if (nls == NULL) { + return NGX_CONF_ERROR; + } + + *nls = *ls; + + } else { + nls = ls; + } - for (i = 0; i < cmcf->listen.nelts - u.naddrs + n; i++) { + nls->sockaddr = u.addrs[n].sockaddr; + nls->socklen = u.addrs[n].socklen; + nls->addr_text = u.addrs[n].name; + nls->wildcard = ngx_inet_wildcard(nls->sockaddr); + + als = cmcf->listen.elts; + + for (i = 0; i < cmcf->listen.nelts - 1; i++) { if (ngx_cmp_sockaddr(als[i].sockaddr, als[i].socklen, - ls[n].sockaddr, ls[n].socklen, 1) + nls->sockaddr, nls->socklen, 1) != NGX_OK) { continue; @@ -589,9 +609,12 @@ ngx_mail_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "duplicate \"%V\" address and port pair", - &ls[n].addr_text); + &nls->addr_text); return NGX_CONF_ERROR; } + + next: + continue; } return NGX_CONF_OK; diff --git a/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c index 246ba97..1167df3 100644 --- a/src/mail/ngx_mail_handler.c +++ b/src/mail/ngx_mail_handler.c @@ -283,11 +283,11 @@ ngx_mail_init_session_handler(ngx_event_t *rev) s = c->data; - sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module); - - if (sslcf->enable || s->ssl) { + if (s->ssl) { c->log->action = "SSL handshaking"; + sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module); + ngx_mail_ssl_init_connection(&sslcf->ssl, c); return; } diff --git a/src/mail/ngx_mail_proxy_module.c b/src/mail/ngx_mail_proxy_module.c index a7ab077..efed9ab 100644 --- a/src/mail/ngx_mail_proxy_module.c +++ b/src/mail/ngx_mail_proxy_module.c @@ -327,7 +327,9 @@ ngx_mail_proxy_pop3_handler(ngx_event_t *rev) c->log->action = NULL; ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in"); - if (s->buffer->pos < s->buffer->last) { + if (s->buffer->pos < s->buffer->last + || s->connection->read->ready) + { ngx_post_event(c->write, &ngx_posted_events); } @@ -486,7 +488,9 @@ ngx_mail_proxy_imap_handler(ngx_event_t *rev) c->log->action = NULL; ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in"); - if (s->buffer->pos < s->buffer->last) { + if (s->buffer->pos < s->buffer->last + || s->connection->read->ready) + { ngx_post_event(c->write, &ngx_posted_events); } @@ -821,7 +825,9 @@ ngx_mail_proxy_smtp_handler(ngx_event_t *rev) c->log->action = NULL; ngx_log_error(NGX_LOG_INFO, c->log, 0, "client logged in"); - if (s->buffer->pos < s->buffer->last) { + if (s->buffer->pos < s->buffer->last + || s->connection->read->ready) + { ngx_post_event(c->write, &ngx_posted_events); } @@ -890,7 +896,7 @@ ngx_mail_proxy_send_proxy_protocol(ngx_mail_session_t *s) u_char *p; ssize_t n, size; ngx_connection_t *c; - u_char buf[NGX_PROXY_PROTOCOL_MAX_HEADER]; + u_char buf[NGX_PROXY_PROTOCOL_V1_MAX_HEADER]; s->connection->log->action = "sending PROXY protocol header to upstream"; @@ -898,7 +904,7 @@ ngx_mail_proxy_send_proxy_protocol(ngx_mail_session_t *s) "mail proxy send PROXY protocol header"); p = ngx_proxy_protocol_write(s->connection, buf, - buf + NGX_PROXY_PROTOCOL_MAX_HEADER); + buf + NGX_PROXY_PROTOCOL_V1_MAX_HEADER); if (p == NULL) { ngx_mail_proxy_internal_server_error(s); return NGX_ERROR; diff --git a/src/mail/ngx_mail_ssl_module.c b/src/mail/ngx_mail_ssl_module.c index 2a1043e..aebb4cc 100644 --- a/src/mail/ngx_mail_ssl_module.c +++ b/src/mail/ngx_mail_ssl_module.c @@ -23,8 +23,6 @@ static int ngx_mail_ssl_alpn_select(ngx_ssl_conn_t *ssl_conn, static void *ngx_mail_ssl_create_conf(ngx_conf_t *cf); static char *ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child); -static char *ngx_mail_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, - void *conf); static char *ngx_mail_ssl_starttls(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_mail_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, @@ -65,24 +63,12 @@ static ngx_conf_enum_t ngx_mail_ssl_verify[] = { }; -static ngx_conf_deprecated_t ngx_mail_ssl_deprecated = { - ngx_conf_deprecated, "ssl", "listen ... ssl" -}; - - static ngx_conf_post_t ngx_mail_ssl_conf_command_post = { ngx_mail_ssl_conf_command_check }; static ngx_command_t ngx_mail_ssl_commands[] = { - { ngx_string("ssl"), - NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG, - ngx_mail_ssl_enable, - NGX_MAIL_SRV_CONF_OFFSET, - offsetof(ngx_mail_ssl_conf_t, enable), - &ngx_mail_ssl_deprecated }, - { ngx_string("starttls"), NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1, ngx_mail_ssl_starttls, @@ -322,7 +308,6 @@ ngx_mail_ssl_create_conf(ngx_conf_t *cf) * scf->shm_zone = NULL; */ - scf->enable = NGX_CONF_UNSET; scf->starttls = NGX_CONF_UNSET_UINT; scf->certificates = NGX_CONF_UNSET_PTR; scf->certificate_keys = NGX_CONF_UNSET_PTR; @@ -349,7 +334,6 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) char *mode; ngx_pool_cleanup_t *cln; - ngx_conf_merge_value(conf->enable, prev->enable, 0); ngx_conf_merge_uint_value(conf->starttls, prev->starttls, NGX_MAIL_STARTTLS_OFF); @@ -360,8 +344,9 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) prev->prefer_server_ciphers, 0); ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols, - (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 - |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); + (NGX_CONF_BITMASK_SET + |NGX_SSL_TLSv1|NGX_SSL_TLSv1_1 + |NGX_SSL_TLSv1_2|NGX_SSL_TLSv1_3)); ngx_conf_merge_uint_value(conf->verify, prev->verify, 0); ngx_conf_merge_uint_value(conf->verify_depth, prev->verify_depth, 1); @@ -393,9 +378,6 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) if (conf->listen) { mode = "listen ... ssl"; - } else if (conf->enable) { - mode = "ssl"; - } else if (conf->starttls != NGX_MAIL_STARTTLS_OFF) { mode = "starttls"; @@ -544,34 +526,6 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) } -static char * -ngx_mail_ssl_enable(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) -{ - ngx_mail_ssl_conf_t *scf = conf; - - char *rv; - - rv = ngx_conf_set_flag_slot(cf, cmd, conf); - - if (rv != NGX_CONF_OK) { - return rv; - } - - if (scf->enable && (ngx_int_t) scf->starttls > NGX_MAIL_STARTTLS_OFF) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"starttls\" directive conflicts with \"ssl on\""); - return NGX_CONF_ERROR; - } - - if (!scf->listen) { - scf->file = cf->conf_file->file.name.data; - scf->line = cf->conf_file->line; - } - - return NGX_CONF_OK; -} - - static char * ngx_mail_ssl_starttls(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { @@ -585,12 +539,6 @@ ngx_mail_ssl_starttls(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return rv; } - if (scf->enable == 1 && (ngx_int_t) scf->starttls > NGX_MAIL_STARTTLS_OFF) { - ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "\"ssl\" directive conflicts with \"starttls\""); - return NGX_CONF_ERROR; - } - if (!scf->listen) { scf->file = cf->conf_file->file.name.data; scf->line = cf->conf_file->line; @@ -682,7 +630,7 @@ ngx_mail_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) len++; } - if (len == 0) { + if (len == 0 || j == value[i].len) { goto invalid; } diff --git a/src/mail/ngx_mail_ssl_module.h b/src/mail/ngx_mail_ssl_module.h index a0a6113..c0eb6a3 100644 --- a/src/mail/ngx_mail_ssl_module.h +++ b/src/mail/ngx_mail_ssl_module.h @@ -20,7 +20,6 @@ typedef struct { - ngx_flag_t enable; ngx_flag_t prefer_server_ciphers; ngx_ssl_t ssl; diff --git a/src/os/unix/ngx_darwin_init.c b/src/os/unix/ngx_darwin_init.c index aabe02f..70748ee 100644 --- a/src/os/unix/ngx_darwin_init.c +++ b/src/os/unix/ngx_darwin_init.c @@ -9,11 +9,12 @@ #include <ngx_core.h> -char ngx_darwin_kern_ostype[16]; -char ngx_darwin_kern_osrelease[128]; -int ngx_darwin_hw_ncpu; -int ngx_darwin_kern_ipc_somaxconn; -u_long ngx_darwin_net_inet_tcp_sendspace; +char ngx_darwin_kern_ostype[16]; +char ngx_darwin_kern_osrelease[128]; +int ngx_darwin_hw_ncpu; +int ngx_darwin_kern_ipc_somaxconn; +u_long ngx_darwin_net_inet_tcp_sendspace; +int64_t ngx_darwin_hw_cachelinesize; ngx_uint_t ngx_debug_malloc; @@ -56,6 +57,10 @@ sysctl_t sysctls[] = { &ngx_darwin_kern_ipc_somaxconn, sizeof(ngx_darwin_kern_ipc_somaxconn), 0 }, + { "hw.cachelinesize", + &ngx_darwin_hw_cachelinesize, + sizeof(ngx_darwin_hw_cachelinesize), 0 }, + { NULL, NULL, 0, 0 } }; @@ -155,6 +160,7 @@ ngx_os_specific_init(ngx_log_t *log) return NGX_ERROR; } + ngx_cacheline_size = ngx_darwin_hw_cachelinesize; ngx_ncpu = ngx_darwin_hw_ncpu; if (ngx_darwin_kern_ipc_somaxconn > 32767) { diff --git a/src/os/unix/ngx_errno.h b/src/os/unix/ngx_errno.h index 7d6ca76..07fa8ce 100644 --- a/src/os/unix/ngx_errno.h +++ b/src/os/unix/ngx_errno.h @@ -54,6 +54,7 @@ typedef int ngx_err_t; #define NGX_ENOMOREFILES 0 #define NGX_ELOOP ELOOP #define NGX_EBADF EBADF +#define NGX_EMSGSIZE EMSGSIZE #if (NGX_HAVE_OPENAT) #define NGX_EMLINK EMLINK diff --git a/src/os/unix/ngx_files.c b/src/os/unix/ngx_files.c index 1c82a8e..2fec1ec 100644 --- a/src/os/unix/ngx_files.c +++ b/src/os/unix/ngx_files.c @@ -110,6 +110,8 @@ ngx_thread_read(ngx_file_t *file, u_char *buf, size_t size, off_t offset, return NGX_ERROR; } + task->event.log = file->log; + file->thread_task = task; } @@ -493,6 +495,8 @@ ngx_thread_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, off_t offset, return NGX_ERROR; } + task->event.log = file->log; + file->thread_task = task; } diff --git a/src/os/unix/ngx_linux_config.h b/src/os/unix/ngx_linux_config.h index 3036cae..88fef47 100644 --- a/src/os/unix/ngx_linux_config.h +++ b/src/os/unix/ngx_linux_config.h @@ -103,6 +103,10 @@ typedef struct iocb ngx_aiocb_t; #include <linux/capability.h> #endif +#if (NGX_HAVE_UDP_SEGMENT) +#include <netinet/udp.h> +#endif + #define NGX_LISTEN_BACKLOG 511 diff --git a/src/os/unix/ngx_linux_sendfile_chain.c b/src/os/unix/ngx_linux_sendfile_chain.c index 101d91a..603f917 100644 --- a/src/os/unix/ngx_linux_sendfile_chain.c +++ b/src/os/unix/ngx_linux_sendfile_chain.c @@ -332,6 +332,7 @@ ngx_linux_sendfile_thread(ngx_connection_t *c, ngx_buf_t *file, size_t size) return NGX_ERROR; } + task->event.log = c->log; task->handler = ngx_linux_sendfile_thread_handler; c->sendfile_task = task; diff --git a/src/os/unix/ngx_posix_init.c b/src/os/unix/ngx_posix_init.c index 7824735..6ac931c 100644 --- a/src/os/unix/ngx_posix_init.c +++ b/src/os/unix/ngx_posix_init.c @@ -51,7 +51,10 @@ ngx_os_init(ngx_log_t *log) } ngx_pagesize = getpagesize(); - ngx_cacheline_size = NGX_CPU_CACHE_LINE; + + if (ngx_cacheline_size == 0) { + ngx_cacheline_size = NGX_CPU_CACHE_LINE; + } for (n = ngx_pagesize; n >>= 1; ngx_pagesize_shift++) { /* void */ } diff --git a/src/os/unix/ngx_process_cycle.c b/src/os/unix/ngx_process_cycle.c index 07cd05e..5bc5ce9 100644 --- a/src/os/unix/ngx_process_cycle.c +++ b/src/os/unix/ngx_process_cycle.c @@ -736,6 +736,7 @@ ngx_worker_process_cycle(ngx_cycle_t *cycle, void *data) ngx_set_shutdown_timer(cycle); ngx_close_listening_sockets(cycle); ngx_close_idle_connections(cycle); + ngx_event_process_posted(cycle, &ngx_posted_events); } } @@ -758,7 +759,6 @@ ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker) ngx_cpuset_t *cpu_affinity; struct rlimit rlmt; ngx_core_conf_t *ccf; - ngx_listening_t *ls; if (ngx_set_environment(cycle, NULL) == NULL) { /* fatal */ @@ -888,15 +888,6 @@ ngx_worker_process_init(ngx_cycle_t *cycle, ngx_int_t worker) tp = ngx_timeofday(); srandom(((unsigned) ngx_pid << 16) ^ tp->sec ^ tp->msec); - /* - * disable deleting previous events for the listening sockets because - * in the worker processes there are no events at all at this point - */ - ls = cycle->listening.elts; - for (i = 0; i < cycle->listening.nelts; i++) { - ls[i].previous = NULL; - } - for (i = 0; cycle->modules[i]; i++) { if (cycle->modules[i]->init_process) { if (cycle->modules[i]->init_process(cycle) == NGX_ERROR) { @@ -957,7 +948,7 @@ ngx_worker_process_exit(ngx_cycle_t *cycle) } } - if (ngx_exiting) { + if (ngx_exiting && !ngx_terminate) { c = cycle->connections; for (i = 0; i < cycle->connection_n; i++) { if (c[i].fd != -1 @@ -972,11 +963,11 @@ ngx_worker_process_exit(ngx_cycle_t *cycle) ngx_debug_quit = 1; } } + } - if (ngx_debug_quit) { - ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "aborting"); - ngx_debug_point(); - } + if (ngx_debug_quit) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "aborting"); + ngx_debug_point(); } /* diff --git a/src/os/unix/ngx_readv_chain.c b/src/os/unix/ngx_readv_chain.c index b1ae4b5..48fa0a7 100644 --- a/src/os/unix/ngx_readv_chain.c +++ b/src/os/unix/ngx_readv_chain.c @@ -46,6 +46,7 @@ ngx_readv_chain(ngx_connection_t *c, ngx_chain_t *chain, off_t limit) return 0; } else { + rev->ready = 0; return NGX_AGAIN; } } @@ -55,12 +56,15 @@ ngx_readv_chain(ngx_connection_t *c, ngx_chain_t *chain, off_t limit) #if (NGX_HAVE_EPOLLRDHUP) - if (ngx_event_flags & NGX_USE_EPOLL_EVENT) { + if ((ngx_event_flags & NGX_USE_EPOLL_EVENT) + && ngx_use_epoll_rdhup) + { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "readv: eof:%d, avail:%d", rev->pending_eof, rev->available); if (rev->available == 0 && !rev->pending_eof) { + rev->ready = 0; return NGX_AGAIN; } } diff --git a/src/os/unix/ngx_recv.c b/src/os/unix/ngx_recv.c index ddfae4d..cc04a5f 100644 --- a/src/os/unix/ngx_recv.c +++ b/src/os/unix/ngx_recv.c @@ -52,7 +52,9 @@ ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size) #if (NGX_HAVE_EPOLLRDHUP) - if (ngx_event_flags & NGX_USE_EPOLL_EVENT) { + if ((ngx_event_flags & NGX_USE_EPOLL_EVENT) + && ngx_use_epoll_rdhup) + { ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, "recv: eof:%d, avail:%d", rev->pending_eof, rev->available); diff --git a/src/os/unix/ngx_socket.h b/src/os/unix/ngx_socket.h index ec66a6f..4480adc 100644 --- a/src/os/unix/ngx_socket.h +++ b/src/os/unix/ngx_socket.h @@ -13,6 +13,8 @@ #define NGX_WRITE_SHUTDOWN SHUT_WR +#define NGX_READ_SHUTDOWN SHUT_RD +#define NGX_RDWR_SHUTDOWN SHUT_RDWR typedef int ngx_socket_t; diff --git a/src/os/unix/ngx_udp_sendmsg_chain.c b/src/os/unix/ngx_udp_sendmsg_chain.c index 3d1d6dd..fa917b5 100644 --- a/src/os/unix/ngx_udp_sendmsg_chain.c +++ b/src/os/unix/ngx_udp_sendmsg_chain.c @@ -12,7 +12,7 @@ static ngx_chain_t *ngx_udp_output_chain_to_iovec(ngx_iovec_t *vec, ngx_chain_t *in, ngx_log_t *log); -static ssize_t ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec); +static ssize_t ngx_sendmsg_vec(ngx_connection_t *c, ngx_iovec_t *vec); ngx_chain_t * @@ -88,7 +88,7 @@ ngx_udp_unix_sendmsg_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) send += vec.size; - n = ngx_sendmsg(c, &vec); + n = ngx_sendmsg_vec(c, &vec); if (n == NGX_ERROR) { return NGX_CHAIN_ERROR; @@ -204,24 +204,13 @@ ngx_udp_output_chain_to_iovec(ngx_iovec_t *vec, ngx_chain_t *in, ngx_log_t *log) static ssize_t -ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec) +ngx_sendmsg_vec(ngx_connection_t *c, ngx_iovec_t *vec) { - ssize_t n; - ngx_err_t err; - struct msghdr msg; - -#if (NGX_HAVE_MSGHDR_MSG_CONTROL) - -#if (NGX_HAVE_IP_SENDSRCADDR) - u_char msg_control[CMSG_SPACE(sizeof(struct in_addr))]; -#elif (NGX_HAVE_IP_PKTINFO) - u_char msg_control[CMSG_SPACE(sizeof(struct in_pktinfo))]; -#endif - -#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) - u_char msg_control6[CMSG_SPACE(sizeof(struct in6_pktinfo))]; -#endif + struct msghdr msg; +#if (NGX_HAVE_ADDRINFO_CMSG) + struct cmsghdr *cmsg; + u_char msg_control[CMSG_SPACE(sizeof(ngx_addrinfo_t))]; #endif ngx_memzero(&msg, sizeof(struct msghdr)); @@ -234,88 +223,180 @@ ngx_sendmsg(ngx_connection_t *c, ngx_iovec_t *vec) msg.msg_iov = vec->iovs; msg.msg_iovlen = vec->count; -#if (NGX_HAVE_MSGHDR_MSG_CONTROL) - +#if (NGX_HAVE_ADDRINFO_CMSG) if (c->listening && c->listening->wildcard && c->local_sockaddr) { + msg.msg_control = msg_control; + msg.msg_controllen = sizeof(msg_control); + ngx_memzero(msg_control, sizeof(msg_control)); + + cmsg = CMSG_FIRSTHDR(&msg); + + msg.msg_controllen = ngx_set_srcaddr_cmsg(cmsg, c->local_sockaddr); + } +#endif + + return ngx_sendmsg(c, &msg, 0); +} + + +#if (NGX_HAVE_ADDRINFO_CMSG) + +size_t +ngx_set_srcaddr_cmsg(struct cmsghdr *cmsg, struct sockaddr *local_sockaddr) +{ + size_t len; #if (NGX_HAVE_IP_SENDSRCADDR) + struct in_addr *addr; + struct sockaddr_in *sin; +#elif (NGX_HAVE_IP_PKTINFO) + struct in_pktinfo *pkt; + struct sockaddr_in *sin; +#endif - if (c->local_sockaddr->sa_family == AF_INET) { - struct cmsghdr *cmsg; - struct in_addr *addr; - struct sockaddr_in *sin; +#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) + struct in6_pktinfo *pkt6; + struct sockaddr_in6 *sin6; +#endif - msg.msg_control = &msg_control; - msg.msg_controllen = sizeof(msg_control); - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_SENDSRCADDR; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); +#if (NGX_HAVE_IP_SENDSRCADDR) || (NGX_HAVE_IP_PKTINFO) - sin = (struct sockaddr_in *) c->local_sockaddr; + if (local_sockaddr->sa_family == AF_INET) { - addr = (struct in_addr *) CMSG_DATA(cmsg); - *addr = sin->sin_addr; - } + cmsg->cmsg_level = IPPROTO_IP; + +#if (NGX_HAVE_IP_SENDSRCADDR) + + cmsg->cmsg_type = IP_SENDSRCADDR; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_addr)); + len = CMSG_SPACE(sizeof(struct in_addr)); + + sin = (struct sockaddr_in *) local_sockaddr; + + addr = (struct in_addr *) CMSG_DATA(cmsg); + *addr = sin->sin_addr; #elif (NGX_HAVE_IP_PKTINFO) - if (c->local_sockaddr->sa_family == AF_INET) { - struct cmsghdr *cmsg; - struct in_pktinfo *pkt; - struct sockaddr_in *sin; + cmsg->cmsg_type = IP_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + len = CMSG_SPACE(sizeof(struct in_pktinfo)); - msg.msg_control = &msg_control; - msg.msg_controllen = sizeof(msg_control); + sin = (struct sockaddr_in *) local_sockaddr; - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = IPPROTO_IP; - cmsg->cmsg_type = IP_PKTINFO; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in_pktinfo)); + pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); + ngx_memzero(pkt, sizeof(struct in_pktinfo)); + pkt->ipi_spec_dst = sin->sin_addr; - sin = (struct sockaddr_in *) c->local_sockaddr; +#endif + return len; + } - pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); - ngx_memzero(pkt, sizeof(struct in_pktinfo)); - pkt->ipi_spec_dst = sin->sin_addr; - } +#endif + +#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) + if (local_sockaddr->sa_family == AF_INET6) { + + cmsg->cmsg_level = IPPROTO_IPV6; + cmsg->cmsg_type = IPV6_PKTINFO; + cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + len = CMSG_SPACE(sizeof(struct in6_pktinfo)); + sin6 = (struct sockaddr_in6 *) local_sockaddr; + + pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); + ngx_memzero(pkt6, sizeof(struct in6_pktinfo)); + pkt6->ipi6_addr = sin6->sin6_addr; + + return len; + } +#endif + + return 0; +} + + +ngx_int_t +ngx_get_srcaddr_cmsg(struct cmsghdr *cmsg, struct sockaddr *local_sockaddr) +{ + +#if (NGX_HAVE_IP_RECVDSTADDR) + struct in_addr *addr; + struct sockaddr_in *sin; +#elif (NGX_HAVE_IP_PKTINFO) + struct in_pktinfo *pkt; + struct sockaddr_in *sin; #endif #if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) + struct in6_pktinfo *pkt6; + struct sockaddr_in6 *sin6; +#endif - if (c->local_sockaddr->sa_family == AF_INET6) { - struct cmsghdr *cmsg; - struct in6_pktinfo *pkt6; - struct sockaddr_in6 *sin6; - msg.msg_control = &msg_control6; - msg.msg_controllen = sizeof(msg_control6); +#if (NGX_HAVE_IP_RECVDSTADDR) - cmsg = CMSG_FIRSTHDR(&msg); - cmsg->cmsg_level = IPPROTO_IPV6; - cmsg->cmsg_type = IPV6_PKTINFO; - cmsg->cmsg_len = CMSG_LEN(sizeof(struct in6_pktinfo)); + if (cmsg->cmsg_level == IPPROTO_IP + && cmsg->cmsg_type == IP_RECVDSTADDR + && local_sockaddr->sa_family == AF_INET) + { + addr = (struct in_addr *) CMSG_DATA(cmsg); + sin = (struct sockaddr_in *) local_sockaddr; + sin->sin_addr = *addr; - sin6 = (struct sockaddr_in6 *) c->local_sockaddr; + return NGX_OK; + } - pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); - ngx_memzero(pkt6, sizeof(struct in6_pktinfo)); - pkt6->ipi6_addr = sin6->sin6_addr; - } +#elif (NGX_HAVE_IP_PKTINFO) + + if (cmsg->cmsg_level == IPPROTO_IP + && cmsg->cmsg_type == IP_PKTINFO + && local_sockaddr->sa_family == AF_INET) + { + pkt = (struct in_pktinfo *) CMSG_DATA(cmsg); + sin = (struct sockaddr_in *) local_sockaddr; + sin->sin_addr = pkt->ipi_addr; + + return NGX_OK; + } #endif + +#if (NGX_HAVE_INET6 && NGX_HAVE_IPV6_RECVPKTINFO) + + if (cmsg->cmsg_level == IPPROTO_IPV6 + && cmsg->cmsg_type == IPV6_PKTINFO + && local_sockaddr->sa_family == AF_INET6) + { + pkt6 = (struct in6_pktinfo *) CMSG_DATA(cmsg); + sin6 = (struct sockaddr_in6 *) local_sockaddr; + sin6->sin6_addr = pkt6->ipi6_addr; + + return NGX_OK; } #endif -eintr: + return NGX_DECLINED; +} - n = sendmsg(c->fd, &msg, 0); +#endif - ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, - "sendmsg: %z of %uz", n, vec->size); + +ssize_t +ngx_sendmsg(ngx_connection_t *c, struct msghdr *msg, int flags) +{ + ssize_t n; + ngx_err_t err; +#if (NGX_DEBUG) + size_t size; + ngx_uint_t i; +#endif + +eintr: + + n = sendmsg(c->fd, msg, flags); if (n == -1) { err = ngx_errno; @@ -338,5 +419,14 @@ eintr: } } +#if (NGX_DEBUG) + for (i = 0, size = 0; i < (size_t) msg->msg_iovlen; i++) { + size += msg->msg_iov[i].iov_len; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "sendmsg: %z of %uz", n, size); +#endif + return n; } diff --git a/src/os/win32/nginx.ico b/src/os/win32/nginx.ico new file mode 100644 index 0000000000000000000000000000000000000000..70f79db1c631c154f1d5002e0f5fc7139a0358bc GIT binary patch literal 1350 zcma)+ziJ~f5XL`80i|0%KtL|9bQN+C*emZbQl$@RuG{Ud2`K}4i#&qiAbt%&USP{D zmQ21+@~j<moKIx*+mS}|FOq=;0@mv@a)EUKt^ll7JADD%ac#M*`YZRI&w+2`nsQ6g zxiWrMXg=G20xyTkiSNd2kcl&yjnbX1*(BZBnSDs_`bD7u{40~F#jf8o@Nl~U!W7NO zI5JQcQ(-fZOu(5k_nAP-v?rUA+@7%{r}MIrU-w^m9AC*D<~Qf<b+kwIF`W{^>3wnH z|Av*p*r%x4m7`O=k?!2EM(NI;y_Mc~ABBuiYpmoVw*co9*!C8f0;?j&$UsTVg+oI& zfHNoVM<6HKlLPCf=b0n_Ez-qIKYRZ*j>mN}xi9(6d3zn3qxzW6m-@XgPK9Uuf`3r& zWkvZxncs^RcH`uG>3ETAAokmONyu>{Df1u8&2JkdDcYEVjZM#1QmR$RZtv&aALRRJ z)OrqPMcieeCncHE*h<QLgEai&^PuDrF;9p?$EQT8R#S3&KkxoZH3y;eseUafE6Uuj z3o5(uw|?nuN_15&uYv1J;5*;r0|VYuxK!4(CmXkyOBFYid&j%Rd-lEsw(r0jfBPQ! R^Y%p9(nf3Ala1T!>?gSO#Y_MI literal 0 HcmV?d00001 diff --git a/src/os/win32/nginx.rc b/src/os/win32/nginx.rc new file mode 100644 index 0000000..dc8b7ab --- /dev/null +++ b/src/os/win32/nginx.rc @@ -0,0 +1,6 @@ + +// Copyright (C) Igor Sysoev +// Copyright (C) Nginx, Inc. + + +nginx icon discardable "src\\os\\win32\\nginx.ico" diff --git a/src/os/win32/nginx_icon16.xpm b/src/os/win32/nginx_icon16.xpm new file mode 100644 index 0000000..45e4bad --- /dev/null +++ b/src/os/win32/nginx_icon16.xpm @@ -0,0 +1,24 @@ +/* XPM */ +static char * nginx_xpm[] = { +"16 16 2 2", +/* colors */ +" c none", +"GG c #009900", +/* pixels */ +" ", +" GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGG ", +" GGGGGG GGGGGG ", +" GGGGGG GGGGGG ", +" GGGGGG ", +" GGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGG GGGGGGGGGGGGGGGGGG ", +" GGGGGG GGGGGGGGGGGGGG ", +" GGGGGG GGGGGG ", +" GGGGGG GGGGGG ", +" GGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG ", +" " +}; diff --git a/src/os/win32/nginx_icon32.xpm b/src/os/win32/nginx_icon32.xpm new file mode 100644 index 0000000..eb26638 --- /dev/null +++ b/src/os/win32/nginx_icon32.xpm @@ -0,0 +1,39 @@ +/* XPM */ +static char * nginx_xpm[] = { +"32 32 2 2", +/* colors */ +" c none", +"GG c #009900", +/* pixels */ +" ", +" ", +" ", +" ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGG ", +" GGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGG GGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" ", +" ", +" ", +" " diff --git a/src/os/win32/nginx_icon48.xpm b/src/os/win32/nginx_icon48.xpm new file mode 100644 index 0000000..c25ba0f --- /dev/null +++ b/src/os/win32/nginx_icon48.xpm @@ -0,0 +1,55 @@ +/* XPM */ +static char * nginx_xpm[] = { +"48 48 2 2", +/* colors */ +" c none", +"GG c #009900", +/* pixels */ +" ", +" ", +" ", +" ", +" ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGG GGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" GGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGGG ", +" ", +" ", +" ", +" ", +" ", diff --git a/src/os/win32/ngx_alloc.c b/src/os/win32/ngx_alloc.c new file mode 100644 index 0000000..0c0ef30 --- /dev/null +++ b/src/os/win32/ngx_alloc.c @@ -0,0 +1,44 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +ngx_uint_t ngx_pagesize; +ngx_uint_t ngx_pagesize_shift; +ngx_uint_t ngx_cacheline_size; + + +void *ngx_alloc(size_t size, ngx_log_t *log) +{ + void *p; + + p = malloc(size); + if (p == NULL) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "malloc(%uz) failed", size); + } + + ngx_log_debug2(NGX_LOG_DEBUG_ALLOC, log, 0, "malloc: %p:%uz", p, size); + + return p; +} + + +void *ngx_calloc(size_t size, ngx_log_t *log) +{ + void *p; + + p = ngx_alloc(size, log); + + if (p) { + ngx_memzero(p, size); + } + + return p; +} diff --git a/src/os/win32/ngx_alloc.h b/src/os/win32/ngx_alloc.h new file mode 100644 index 0000000..5a0fa3f --- /dev/null +++ b/src/os/win32/ngx_alloc.h @@ -0,0 +1,27 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_ALLOC_H_INCLUDED_ +#define _NGX_ALLOC_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +void *ngx_alloc(size_t size, ngx_log_t *log); +void *ngx_calloc(size_t size, ngx_log_t *log); + +#define ngx_free free +#define ngx_memalign(alignment, size, log) ngx_alloc(size, log) + +extern ngx_uint_t ngx_pagesize; +extern ngx_uint_t ngx_pagesize_shift; +extern ngx_uint_t ngx_cacheline_size; + + +#endif /* _NGX_ALLOC_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_atomic.h b/src/os/win32/ngx_atomic.h new file mode 100644 index 0000000..113f561 --- /dev/null +++ b/src/os/win32/ngx_atomic.h @@ -0,0 +1,69 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_ATOMIC_H_INCLUDED_ +#define _NGX_ATOMIC_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +#define NGX_HAVE_ATOMIC_OPS 1 + +typedef int32_t ngx_atomic_int_t; +typedef uint32_t ngx_atomic_uint_t; +typedef volatile ngx_atomic_uint_t ngx_atomic_t; +#define NGX_ATOMIC_T_LEN (sizeof("-2147483648") - 1) + + +#if defined( __WATCOMC__ ) || defined( __BORLANDC__ ) || defined(__GNUC__) \ + || ( _MSC_VER >= 1300 ) + +/* the new SDK headers */ + +#define ngx_atomic_cmp_set(lock, old, set) \ + ((ngx_atomic_uint_t) InterlockedCompareExchange((long *) lock, set, old) \ + == old) + +#else + +/* the old MS VC6.0SP2 SDK headers */ + +#define ngx_atomic_cmp_set(lock, old, set) \ + (InterlockedCompareExchange((void **) lock, (void *) set, (void *) old) \ + == (void *) old) + +#endif + + +#define ngx_atomic_fetch_add(p, add) InterlockedExchangeAdd((long *) p, add) + + +#define ngx_memory_barrier() + + +#if defined( __BORLANDC__ ) || ( __WATCOMC__ < 1230 ) + +/* + * Borland C++ 5.5 (tasm32) and Open Watcom C prior to 1.3 + * do not understand the "pause" instruction + */ + +#define ngx_cpu_pause() +#else +#define ngx_cpu_pause() __asm { pause } +#endif + + +void ngx_spinlock(ngx_atomic_t *lock, ngx_atomic_int_t value, ngx_uint_t spin); + +#define ngx_trylock(lock) (*(lock) == 0 && ngx_atomic_cmp_set(lock, 0, 1)) +#define ngx_unlock(lock) *(lock) = 0 + + +#endif /* _NGX_ATOMIC_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_dlopen.c b/src/os/win32/ngx_dlopen.c new file mode 100644 index 0000000..804f49d --- /dev/null +++ b/src/os/win32/ngx_dlopen.c @@ -0,0 +1,22 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +char * +ngx_dlerror(void) +{ + u_char *p; + static u_char errstr[NGX_MAX_ERROR_STR]; + + p = ngx_strerror(ngx_errno, errstr, NGX_MAX_ERROR_STR); + *p = '\0'; + + return (char *) errstr; +} diff --git a/src/os/win32/ngx_dlopen.h b/src/os/win32/ngx_dlopen.h new file mode 100644 index 0000000..0d6b405 --- /dev/null +++ b/src/os/win32/ngx_dlopen.h @@ -0,0 +1,32 @@ + +/* + * Copyright (C) Maxim Dounin + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_DLOPEN_H_INCLUDED_ +#define _NGX_DLOPEN_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +#define NGX_HAVE_DLOPEN 1 + + +#define ngx_dlopen(path) LoadLibrary((char *) path) +#define ngx_dlopen_n "LoadLibrary()" + +#define ngx_dlsym(handle, symbol) (void *) GetProcAddress(handle, symbol) +#define ngx_dlsym_n "GetProcAddress()" + +#define ngx_dlclose(handle) (FreeLibrary(handle) ? 0 : -1) +#define ngx_dlclose_n "FreeLibrary()" + + +char *ngx_dlerror(void); + + +#endif /* _NGX_DLOPEN_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_errno.c b/src/os/win32/ngx_errno.c new file mode 100644 index 0000000..966d3de --- /dev/null +++ b/src/os/win32/ngx_errno.c @@ -0,0 +1,60 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +u_char * +ngx_strerror(ngx_err_t err, u_char *errstr, size_t size) +{ + u_int len; + static u_long lang = MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US); + + if (size == 0) { + return errstr; + } + + len = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + NULL, err, lang, (char *) errstr, size, NULL); + + if (len == 0 && lang) { + + /* + * Try to use English messages first and fallback to a language, + * based on locale: non-English Windows have no English messages + * at all. This way allows to use English messages at least on + * Windows with MUI. + */ + + lang = 0; + + len = FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, + NULL, err, lang, (char *) errstr, size, NULL); + } + + if (len == 0) { + return ngx_snprintf(errstr, size, + "FormatMessage() error:(%d)", GetLastError()); + } + + /* remove ".\r\n\0" */ + while (errstr[len] == '\0' || errstr[len] == CR + || errstr[len] == LF || errstr[len] == '.') + { + --len; + } + + return &errstr[++len]; +} + + +ngx_int_t +ngx_strerror_init(void) +{ + return NGX_OK; +} diff --git a/src/os/win32/ngx_errno.h b/src/os/win32/ngx_errno.h new file mode 100644 index 0000000..1e73a83 --- /dev/null +++ b/src/os/win32/ngx_errno.h @@ -0,0 +1,72 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_ERRNO_H_INCLUDED_ +#define _NGX_ERRNO_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +typedef DWORD ngx_err_t; + +#define ngx_errno GetLastError() +#define ngx_set_errno(err) SetLastError(err) +#define ngx_socket_errno WSAGetLastError() +#define ngx_set_socket_errno(err) WSASetLastError(err) + +#define NGX_EPERM ERROR_ACCESS_DENIED +#define NGX_ENOENT ERROR_FILE_NOT_FOUND +#define NGX_ENOPATH ERROR_PATH_NOT_FOUND +#define NGX_ENOMEM ERROR_NOT_ENOUGH_MEMORY +#define NGX_EACCES ERROR_ACCESS_DENIED +/* + * there are two EEXIST error codes: + * ERROR_FILE_EXISTS used by CreateFile(CREATE_NEW), + * and ERROR_ALREADY_EXISTS used by CreateDirectory(); + * MoveFile() uses both + */ +#define NGX_EEXIST ERROR_ALREADY_EXISTS +#define NGX_EEXIST_FILE ERROR_FILE_EXISTS +#define NGX_EXDEV ERROR_NOT_SAME_DEVICE +#define NGX_ENOTDIR ERROR_PATH_NOT_FOUND +#define NGX_EISDIR ERROR_CANNOT_MAKE +#define NGX_ENOSPC ERROR_DISK_FULL +#define NGX_EPIPE EPIPE +#define NGX_EAGAIN WSAEWOULDBLOCK +#define NGX_EINPROGRESS WSAEINPROGRESS +#define NGX_ENOPROTOOPT WSAENOPROTOOPT +#define NGX_EOPNOTSUPP WSAEOPNOTSUPP +#define NGX_EADDRINUSE WSAEADDRINUSE +#define NGX_ECONNABORTED WSAECONNABORTED +#define NGX_ECONNRESET WSAECONNRESET +#define NGX_ENOTCONN WSAENOTCONN +#define NGX_ETIMEDOUT WSAETIMEDOUT +#define NGX_ECONNREFUSED WSAECONNREFUSED +#define NGX_ENAMETOOLONG ERROR_BAD_PATHNAME +#define NGX_ENETDOWN WSAENETDOWN +#define NGX_ENETUNREACH WSAENETUNREACH +#define NGX_EHOSTDOWN WSAEHOSTDOWN +#define NGX_EHOSTUNREACH WSAEHOSTUNREACH +#define NGX_ENOMOREFILES ERROR_NO_MORE_FILES +#define NGX_EILSEQ ERROR_NO_UNICODE_TRANSLATION +#define NGX_ELOOP 0 +#define NGX_EBADF WSAEBADF +#define NGX_EMSGSIZE WSAEMSGSIZE + +#define NGX_EALREADY WSAEALREADY +#define NGX_EINVAL WSAEINVAL +#define NGX_EMFILE WSAEMFILE +#define NGX_ENFILE WSAEMFILE + + +u_char *ngx_strerror(ngx_err_t err, u_char *errstr, size_t size); +ngx_int_t ngx_strerror_init(void); + + +#endif /* _NGX_ERRNO_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_event_log.c b/src/os/win32/ngx_event_log.c new file mode 100644 index 0000000..e11ed1e --- /dev/null +++ b/src/os/win32/ngx_event_log.c @@ -0,0 +1,99 @@ +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +#define NGX_MAX_ERROR_STR 2048 + + +void ngx_cdecl +ngx_event_log(ngx_err_t err, const char *fmt, ...) +{ + u_char *p, *last; + long types; + HKEY key; + HANDLE ev; + va_list args; + u_char text[NGX_MAX_ERROR_STR]; + const char *msgarg[9]; + static u_char netmsg[] = "%SystemRoot%\\System32\\netmsg.dll"; + + last = text + NGX_MAX_ERROR_STR; + p = text + GetModuleFileName(NULL, (char *) text, NGX_MAX_ERROR_STR - 50); + + *p++ = ':'; + ngx_linefeed(p); + + va_start(args, fmt); + p = ngx_vslprintf(p, last, fmt, args); + va_end(args); + + if (err) { + p = ngx_log_errno(p, last, err); + } + + if (p > last - NGX_LINEFEED_SIZE - 1) { + p = last - NGX_LINEFEED_SIZE - 1; + } + + ngx_linefeed(p); + + *p = '\0'; + + /* + * we do not log errors here since we use + * Event Log only to log our own logs open errors + */ + + if (RegCreateKeyEx(HKEY_LOCAL_MACHINE, + "SYSTEM\\CurrentControlSet\\Services\\EventLog\\Application\\nginx", + 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &key, NULL) + != 0) + { + return; + } + + if (RegSetValueEx(key, "EventMessageFile", 0, REG_EXPAND_SZ, + netmsg, sizeof(netmsg) - 1) + != 0) + { + return; + } + + types = EVENTLOG_ERROR_TYPE; + + if (RegSetValueEx(key, "TypesSupported", 0, REG_DWORD, + (u_char *) &types, sizeof(long)) + != 0) + { + return; + } + + RegCloseKey(key); + + ev = RegisterEventSource(NULL, "nginx"); + + msgarg[0] = (char *) text; + msgarg[1] = NULL; + msgarg[2] = NULL; + msgarg[3] = NULL; + msgarg[4] = NULL; + msgarg[5] = NULL; + msgarg[6] = NULL; + msgarg[7] = NULL; + msgarg[8] = NULL; + + /* + * the 3299 event id in netmsg.dll has the generic message format: + * "%1 %2 %3 %4 %5 %6 %7 %8 %9" + */ + + ReportEvent(ev, EVENTLOG_ERROR_TYPE, 0, 3299, NULL, 9, 0, msgarg, NULL); + + DeregisterEventSource(ev); +} diff --git a/src/os/win32/ngx_files.c b/src/os/win32/ngx_files.c new file mode 100644 index 0000000..90644ad --- /dev/null +++ b/src/os/win32/ngx_files.c @@ -0,0 +1,1459 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +#define NGX_UTF16_BUFLEN 256 +#define NGX_UTF8_BUFLEN 512 + +static ngx_int_t ngx_win32_check_filename(u_short *u, size_t len, + ngx_uint_t dirname); +static u_short *ngx_utf8_to_utf16(u_short *utf16, u_char *utf8, size_t *len, + size_t reserved); +static u_char *ngx_utf16_to_utf8(u_char *utf8, u_short *utf16, size_t *len, + size_t *allocated); +uint32_t ngx_utf16_decode(u_short **u, size_t n); + + +/* FILE_FLAG_BACKUP_SEMANTICS allows to obtain a handle to a directory */ + +ngx_fd_t +ngx_open_file(u_char *name, u_long mode, u_long create, u_long access) +{ + size_t len; + u_short *u; + ngx_fd_t fd; + ngx_err_t err; + u_short utf16[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN; + u = ngx_utf8_to_utf16(utf16, name, &len, 0); + + if (u == NULL) { + return INVALID_HANDLE_VALUE; + } + + fd = INVALID_HANDLE_VALUE; + + if (create == NGX_FILE_OPEN + && ngx_win32_check_filename(u, len, 0) != NGX_OK) + { + goto failed; + } + + fd = CreateFileW(u, mode, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + NULL, create, FILE_FLAG_BACKUP_SEMANTICS, NULL); + +failed: + + if (u != utf16) { + err = ngx_errno; + ngx_free(u); + ngx_set_errno(err); + } + + return fd; +} + + +ngx_fd_t +ngx_open_tempfile(u_char *name, ngx_uint_t persistent, ngx_uint_t access) +{ + size_t len; + u_short *u; + ngx_fd_t fd; + ngx_err_t err; + u_short utf16[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN; + u = ngx_utf8_to_utf16(utf16, name, &len, 0); + + if (u == NULL) { + return INVALID_HANDLE_VALUE; + } + + fd = CreateFileW(u, + GENERIC_READ|GENERIC_WRITE, + FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE, + NULL, + CREATE_NEW, + persistent ? 0: + FILE_ATTRIBUTE_TEMPORARY|FILE_FLAG_DELETE_ON_CLOSE, + NULL); + + if (u != utf16) { + err = ngx_errno; + ngx_free(u); + ngx_set_errno(err); + } + + return fd; +} + + +ssize_t +ngx_read_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset) +{ + u_long n; + ngx_err_t err; + OVERLAPPED ovlp, *povlp; + + ovlp.Internal = 0; + ovlp.InternalHigh = 0; + ovlp.Offset = (u_long) offset; + ovlp.OffsetHigh = (u_long) (offset >> 32); + ovlp.hEvent = NULL; + + povlp = &ovlp; + + if (ReadFile(file->fd, buf, size, &n, povlp) == 0) { + err = ngx_errno; + + if (err == ERROR_HANDLE_EOF) { + return 0; + } + + ngx_log_error(NGX_LOG_ERR, file->log, err, + "ReadFile() \"%s\" failed", file->name.data); + return NGX_ERROR; + } + + file->offset += n; + + return n; +} + + +ssize_t +ngx_write_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset) +{ + u_long n; + OVERLAPPED ovlp, *povlp; + + ovlp.Internal = 0; + ovlp.InternalHigh = 0; + ovlp.Offset = (u_long) offset; + ovlp.OffsetHigh = (u_long) (offset >> 32); + ovlp.hEvent = NULL; + + povlp = &ovlp; + + if (WriteFile(file->fd, buf, size, &n, povlp) == 0) { + ngx_log_error(NGX_LOG_ERR, file->log, ngx_errno, + "WriteFile() \"%s\" failed", file->name.data); + return NGX_ERROR; + } + + if (n != size) { + ngx_log_error(NGX_LOG_CRIT, file->log, 0, + "WriteFile() \"%s\" has written only %ul of %uz", + file->name.data, n, size); + return NGX_ERROR; + } + + file->offset += n; + + return n; +} + + +ssize_t +ngx_write_chain_to_file(ngx_file_t *file, ngx_chain_t *cl, off_t offset, + ngx_pool_t *pool) +{ + u_char *buf, *prev; + size_t size; + ssize_t total, n; + + total = 0; + + while (cl) { + buf = cl->buf->pos; + prev = buf; + size = 0; + + /* coalesce the neighbouring bufs */ + + while (cl && prev == cl->buf->pos) { + size += cl->buf->last - cl->buf->pos; + prev = cl->buf->last; + cl = cl->next; + } + + n = ngx_write_file(file, buf, size, offset); + + if (n == NGX_ERROR) { + return NGX_ERROR; + } + + total += n; + offset += n; + } + + return total; +} + + +ssize_t +ngx_read_fd(ngx_fd_t fd, void *buf, size_t size) +{ + u_long n; + + if (ReadFile(fd, buf, size, &n, NULL) != 0) { + return (size_t) n; + } + + return -1; +} + + +ssize_t +ngx_write_fd(ngx_fd_t fd, void *buf, size_t size) +{ + u_long n; + + if (WriteFile(fd, buf, size, &n, NULL) != 0) { + return (size_t) n; + } + + return -1; +} + + +ssize_t +ngx_write_console(ngx_fd_t fd, void *buf, size_t size) +{ + u_long n; + + (void) CharToOemBuff(buf, buf, size); + + if (WriteFile(fd, buf, size, &n, NULL) != 0) { + return (size_t) n; + } + + return -1; +} + + +ngx_int_t +ngx_delete_file(u_char *name) +{ + long rc; + size_t len; + u_short *u; + ngx_err_t err; + u_short utf16[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN; + u = ngx_utf8_to_utf16(utf16, name, &len, 0); + + if (u == NULL) { + return NGX_FILE_ERROR; + } + + rc = NGX_FILE_ERROR; + + if (ngx_win32_check_filename(u, len, 0) != NGX_OK) { + goto failed; + } + + rc = DeleteFileW(u); + +failed: + + if (u != utf16) { + err = ngx_errno; + ngx_free(u); + ngx_set_errno(err); + } + + return rc; +} + + +ngx_int_t +ngx_rename_file(u_char *from, u_char *to) +{ + long rc; + size_t len; + u_short *fu, *tu; + ngx_err_t err; + u_short utf16f[NGX_UTF16_BUFLEN]; + u_short utf16t[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN; + fu = ngx_utf8_to_utf16(utf16f, from, &len, 0); + + if (fu == NULL) { + return NGX_FILE_ERROR; + } + + rc = NGX_FILE_ERROR; + tu = NULL; + + if (ngx_win32_check_filename(fu, len, 0) != NGX_OK) { + goto failed; + } + + len = NGX_UTF16_BUFLEN; + tu = ngx_utf8_to_utf16(utf16t, to, &len, 0); + + if (tu == NULL) { + goto failed; + } + + if (ngx_win32_check_filename(tu, len, 1) != NGX_OK) { + goto failed; + } + + rc = MoveFileW(fu, tu); + +failed: + + if (fu != utf16f) { + err = ngx_errno; + ngx_free(fu); + ngx_set_errno(err); + } + + if (tu && tu != utf16t) { + err = ngx_errno; + ngx_free(tu); + ngx_set_errno(err); + } + + return rc; +} + + +ngx_err_t +ngx_win32_rename_file(ngx_str_t *from, ngx_str_t *to, ngx_log_t *log) +{ + u_char *name; + ngx_err_t err; + ngx_uint_t collision; + ngx_atomic_uint_t num; + + name = ngx_alloc(to->len + 1 + NGX_ATOMIC_T_LEN + 1 + sizeof("DELETE"), + log); + if (name == NULL) { + return NGX_ENOMEM; + } + + ngx_memcpy(name, to->data, to->len); + + collision = 0; + + /* mutex_lock() (per cache or single ?) */ + + for ( ;; ) { + num = ngx_next_temp_number(collision); + + ngx_sprintf(name + to->len, ".%0muA.DELETE%Z", num); + + if (ngx_rename_file(to->data, name) != NGX_FILE_ERROR) { + break; + } + + err = ngx_errno; + + if (err == NGX_EEXIST || err == NGX_EEXIST_FILE) { + collision = 1; + continue; + } + + ngx_log_error(NGX_LOG_CRIT, log, err, + "MoveFile() \"%s\" to \"%s\" failed", to->data, name); + goto failed; + } + + if (ngx_rename_file(from->data, to->data) == NGX_FILE_ERROR) { + err = ngx_errno; + + } else { + err = 0; + } + + if (ngx_delete_file(name) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_CRIT, log, ngx_errno, + "DeleteFile() \"%s\" failed", name); + } + +failed: + + /* mutex_unlock() */ + + ngx_free(name); + + return err; +} + + +ngx_int_t +ngx_file_info(u_char *file, ngx_file_info_t *sb) +{ + size_t len; + long rc; + u_short *u; + ngx_err_t err; + WIN32_FILE_ATTRIBUTE_DATA fa; + u_short utf16[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN; + + u = ngx_utf8_to_utf16(utf16, file, &len, 0); + + if (u == NULL) { + return NGX_FILE_ERROR; + } + + rc = NGX_FILE_ERROR; + + if (ngx_win32_check_filename(u, len, 0) != NGX_OK) { + goto failed; + } + + rc = GetFileAttributesExW(u, GetFileExInfoStandard, &fa); + + sb->dwFileAttributes = fa.dwFileAttributes; + sb->ftCreationTime = fa.ftCreationTime; + sb->ftLastAccessTime = fa.ftLastAccessTime; + sb->ftLastWriteTime = fa.ftLastWriteTime; + sb->nFileSizeHigh = fa.nFileSizeHigh; + sb->nFileSizeLow = fa.nFileSizeLow; + +failed: + + if (u != utf16) { + err = ngx_errno; + ngx_free(u); + ngx_set_errno(err); + } + + return rc; +} + + +ngx_int_t +ngx_set_file_time(u_char *name, ngx_fd_t fd, time_t s) +{ + uint64_t intervals; + FILETIME ft; + + /* 116444736000000000 is commented in src/os/win32/ngx_time.c */ + + intervals = s * 10000000 + 116444736000000000; + + ft.dwLowDateTime = (DWORD) intervals; + ft.dwHighDateTime = (DWORD) (intervals >> 32); + + if (SetFileTime(fd, NULL, NULL, &ft) != 0) { + return NGX_OK; + } + + return NGX_ERROR; +} + + +ngx_int_t +ngx_create_file_mapping(ngx_file_mapping_t *fm) +{ + LARGE_INTEGER size; + + fm->fd = ngx_open_file(fm->name, NGX_FILE_RDWR, NGX_FILE_TRUNCATE, + NGX_FILE_DEFAULT_ACCESS); + + if (fm->fd == NGX_INVALID_FILE) { + ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno, + ngx_open_file_n " \"%s\" failed", fm->name); + return NGX_ERROR; + } + + fm->handle = NULL; + + size.QuadPart = fm->size; + + if (SetFilePointerEx(fm->fd, size, NULL, FILE_BEGIN) == 0) { + ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno, + "SetFilePointerEx(\"%s\", %uz) failed", + fm->name, fm->size); + goto failed; + } + + if (SetEndOfFile(fm->fd) == 0) { + ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno, + "SetEndOfFile() \"%s\" failed", fm->name); + goto failed; + } + + fm->handle = CreateFileMapping(fm->fd, NULL, PAGE_READWRITE, + (u_long) ((off_t) fm->size >> 32), + (u_long) ((off_t) fm->size & 0xffffffff), + NULL); + if (fm->handle == NULL) { + ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno, + "CreateFileMapping(%s, %uz) failed", + fm->name, fm->size); + goto failed; + } + + fm->addr = MapViewOfFile(fm->handle, FILE_MAP_WRITE, 0, 0, 0); + + if (fm->addr != NULL) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_CRIT, fm->log, ngx_errno, + "MapViewOfFile(%uz) of file mapping \"%s\" failed", + fm->size, fm->name); + +failed: + + if (fm->handle) { + if (CloseHandle(fm->handle) == 0) { + ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno, + "CloseHandle() of file mapping \"%s\" failed", + fm->name); + } + } + + if (ngx_close_file(fm->fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno, + ngx_close_file_n " \"%s\" failed", fm->name); + } + + return NGX_ERROR; +} + + +void +ngx_close_file_mapping(ngx_file_mapping_t *fm) +{ + if (UnmapViewOfFile(fm->addr) == 0) { + ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno, + "UnmapViewOfFile(%p) of file mapping \"%s\" failed", + fm->addr, &fm->name); + } + + if (CloseHandle(fm->handle) == 0) { + ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno, + "CloseHandle() of file mapping \"%s\" failed", + &fm->name); + } + + if (ngx_close_file(fm->fd) == NGX_FILE_ERROR) { + ngx_log_error(NGX_LOG_ALERT, fm->log, ngx_errno, + ngx_close_file_n " \"%s\" failed", fm->name); + } +} + + +u_char * +ngx_realpath(u_char *path, u_char *resolved) +{ + /* STUB */ + return path; +} + + +size_t +ngx_getcwd(u_char *buf, size_t size) +{ + u_char *p; + size_t n; + u_short utf16[NGX_MAX_PATH]; + + n = GetCurrentDirectoryW(NGX_MAX_PATH, utf16); + + if (n == 0) { + return 0; + } + + if (n > NGX_MAX_PATH) { + ngx_set_errno(ERROR_INSUFFICIENT_BUFFER); + return 0; + } + + p = ngx_utf16_to_utf8(buf, utf16, &size, NULL); + + if (p == NULL) { + return 0; + } + + if (p != buf) { + ngx_free(p); + ngx_set_errno(ERROR_INSUFFICIENT_BUFFER); + return 0; + } + + return size - 1; +} + + +ngx_int_t +ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir) +{ + size_t len; + u_short *u, *p; + ngx_err_t err; + u_short utf16[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN - 2; + u = ngx_utf8_to_utf16(utf16, name->data, &len, 2); + + if (u == NULL) { + return NGX_ERROR; + } + + if (ngx_win32_check_filename(u, len, 0) != NGX_OK) { + goto failed; + } + + p = &u[len - 1]; + + *p++ = '/'; + *p++ = '*'; + *p = '\0'; + + dir->dir = FindFirstFileW(u, &dir->finddata); + + if (dir->dir == INVALID_HANDLE_VALUE) { + goto failed; + } + + if (u != utf16) { + ngx_free(u); + } + + dir->valid_info = 1; + dir->ready = 1; + dir->name = NULL; + dir->allocated = 0; + + return NGX_OK; + +failed: + + if (u != utf16) { + err = ngx_errno; + ngx_free(u); + ngx_set_errno(err); + } + + return NGX_ERROR; +} + + +ngx_int_t +ngx_read_dir(ngx_dir_t *dir) +{ + u_char *name; + size_t len, allocated; + + if (dir->ready) { + dir->ready = 0; + goto convert; + } + + if (FindNextFileW(dir->dir, &dir->finddata) != 0) { + dir->type = 1; + goto convert; + } + + return NGX_ERROR; + +convert: + + name = dir->name; + len = dir->allocated; + + name = ngx_utf16_to_utf8(name, dir->finddata.cFileName, &len, &allocated); + + if (name == NULL) { + return NGX_ERROR; + } + + if (name != dir->name) { + + if (dir->name) { + ngx_free(dir->name); + } + + dir->name = name; + dir->allocated = allocated; + } + + dir->namelen = len - 1; + + return NGX_OK; +} + + +ngx_int_t +ngx_close_dir(ngx_dir_t *dir) +{ + if (dir->name) { + ngx_free(dir->name); + } + + if (FindClose(dir->dir) == 0) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +ngx_int_t +ngx_create_dir(u_char *name, ngx_uint_t access) +{ + long rc; + size_t len; + u_short *u; + ngx_err_t err; + u_short utf16[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN; + u = ngx_utf8_to_utf16(utf16, name, &len, 0); + + if (u == NULL) { + return NGX_FILE_ERROR; + } + + rc = NGX_FILE_ERROR; + + if (ngx_win32_check_filename(u, len, 1) != NGX_OK) { + goto failed; + } + + rc = CreateDirectoryW(u, NULL); + +failed: + + if (u != utf16) { + err = ngx_errno; + ngx_free(u); + ngx_set_errno(err); + } + + return rc; +} + + +ngx_int_t +ngx_delete_dir(u_char *name) +{ + long rc; + size_t len; + u_short *u; + ngx_err_t err; + u_short utf16[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN; + u = ngx_utf8_to_utf16(utf16, name, &len, 0); + + if (u == NULL) { + return NGX_FILE_ERROR; + } + + rc = NGX_FILE_ERROR; + + if (ngx_win32_check_filename(u, len, 0) != NGX_OK) { + goto failed; + } + + rc = RemoveDirectoryW(u); + +failed: + + if (u != utf16) { + err = ngx_errno; + ngx_free(u); + ngx_set_errno(err); + } + + return rc; +} + + +ngx_int_t +ngx_open_glob(ngx_glob_t *gl) +{ + u_char *p; + size_t len; + u_short *u; + ngx_err_t err; + u_short utf16[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN; + u = ngx_utf8_to_utf16(utf16, gl->pattern, &len, 0); + + if (u == NULL) { + return NGX_ERROR; + } + + gl->dir = FindFirstFileW(u, &gl->finddata); + + if (gl->dir == INVALID_HANDLE_VALUE) { + + err = ngx_errno; + + if (u != utf16) { + ngx_free(u); + } + + if ((err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) + && gl->test) + { + gl->no_match = 1; + return NGX_OK; + } + + ngx_set_errno(err); + + return NGX_ERROR; + } + + for (p = gl->pattern; *p; p++) { + if (*p == '/') { + gl->last = p + 1 - gl->pattern; + } + } + + if (u != utf16) { + ngx_free(u); + } + + gl->ready = 1; + + return NGX_OK; +} + + +ngx_int_t +ngx_read_glob(ngx_glob_t *gl, ngx_str_t *name) +{ + u_char *p; + size_t len; + ngx_err_t err; + u_char utf8[NGX_UTF8_BUFLEN]; + + if (gl->no_match) { + return NGX_DONE; + } + + if (gl->ready) { + gl->ready = 0; + goto convert; + } + + ngx_free(gl->name.data); + gl->name.data = NULL; + + if (FindNextFileW(gl->dir, &gl->finddata) != 0) { + goto convert; + } + + err = ngx_errno; + + if (err == NGX_ENOMOREFILES) { + return NGX_DONE; + } + + ngx_log_error(NGX_LOG_ALERT, gl->log, err, + "FindNextFile(%s) failed", gl->pattern); + + return NGX_ERROR; + +convert: + + len = NGX_UTF8_BUFLEN; + p = ngx_utf16_to_utf8(utf8, gl->finddata.cFileName, &len, NULL); + + if (p == NULL) { + return NGX_ERROR; + } + + gl->name.len = gl->last + len - 1; + + gl->name.data = ngx_alloc(gl->name.len + 1, gl->log); + if (gl->name.data == NULL) { + goto failed; + } + + ngx_memcpy(gl->name.data, gl->pattern, gl->last); + ngx_cpystrn(gl->name.data + gl->last, p, len); + + if (p != utf8) { + ngx_free(p); + } + + *name = gl->name; + + return NGX_OK; + +failed: + + if (p != utf8) { + err = ngx_errno; + ngx_free(p); + ngx_set_errno(err); + } + + return NGX_ERROR; +} + + +void +ngx_close_glob(ngx_glob_t *gl) +{ + if (gl->name.data) { + ngx_free(gl->name.data); + } + + if (gl->dir == INVALID_HANDLE_VALUE) { + return; + } + + if (FindClose(gl->dir) == 0) { + ngx_log_error(NGX_LOG_ALERT, gl->log, ngx_errno, + "FindClose(%s) failed", gl->pattern); + } +} + + +ngx_int_t +ngx_de_info(u_char *name, ngx_dir_t *dir) +{ + return NGX_OK; +} + + +ngx_int_t +ngx_de_link_info(u_char *name, ngx_dir_t *dir) +{ + return NGX_OK; +} + + +ngx_int_t +ngx_read_ahead(ngx_fd_t fd, size_t n) +{ + return ~NGX_FILE_ERROR; +} + + +ngx_int_t +ngx_directio_on(ngx_fd_t fd) +{ + return ~NGX_FILE_ERROR; +} + + +ngx_int_t +ngx_directio_off(ngx_fd_t fd) +{ + return ~NGX_FILE_ERROR; +} + + +size_t +ngx_fs_bsize(u_char *name) +{ + u_long sc, bs, nfree, ncl; + size_t len; + u_short *u; + u_short utf16[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN; + u = ngx_utf8_to_utf16(utf16, name, &len, 0); + + if (u == NULL) { + return 512; + } + + if (GetDiskFreeSpaceW(u, &sc, &bs, &nfree, &ncl) == 0) { + + if (u != utf16) { + ngx_free(u); + } + + return 512; + } + + if (u != utf16) { + ngx_free(u); + } + + return sc * bs; +} + + +off_t +ngx_fs_available(u_char *name) +{ + size_t len; + u_short *u; + ULARGE_INTEGER navail; + u_short utf16[NGX_UTF16_BUFLEN]; + + len = NGX_UTF16_BUFLEN; + u = ngx_utf8_to_utf16(utf16, name, &len, 0); + + if (u == NULL) { + return NGX_MAX_OFF_T_VALUE; + } + + if (GetDiskFreeSpaceExW(u, &navail, NULL, NULL) == 0) { + + if (u != utf16) { + ngx_free(u); + } + + return NGX_MAX_OFF_T_VALUE; + } + + if (u != utf16) { + ngx_free(u); + } + + return (off_t) navail.QuadPart; +} + + +static ngx_int_t +ngx_win32_check_filename(u_short *u, size_t len, ngx_uint_t dirname) +{ + u_long n; + u_short *lu, *p, *slash, ch; + ngx_err_t err; + enum { + sw_start = 0, + sw_normal, + sw_after_slash, + sw_after_colon, + sw_after_dot + } state; + + /* check for NTFS streams (":"), trailing dots and spaces */ + + lu = NULL; + slash = NULL; + state = sw_start; + +#if (NGX_SUPPRESS_WARN) + ch = 0; +#endif + + for (p = u; *p; p++) { + ch = *p; + + switch (state) { + + case sw_start: + + /* + * skip till first "/" to allow paths starting with drive and + * relative path, like "c:html/" + */ + + if (ch == '/' || ch == '\\') { + state = sw_after_slash; + slash = p; + } + + break; + + case sw_normal: + + if (ch == ':') { + state = sw_after_colon; + break; + } + + if (ch == '.' || ch == ' ') { + state = sw_after_dot; + break; + } + + if (ch == '/' || ch == '\\') { + state = sw_after_slash; + slash = p; + break; + } + + break; + + case sw_after_slash: + + if (ch == '/' || ch == '\\') { + break; + } + + if (ch == '.') { + break; + } + + if (ch == ':') { + state = sw_after_colon; + break; + } + + state = sw_normal; + break; + + case sw_after_colon: + + if (ch == '/' || ch == '\\') { + state = sw_after_slash; + slash = p; + break; + } + + goto invalid; + + case sw_after_dot: + + if (ch == '/' || ch == '\\') { + goto invalid; + } + + if (ch == ':') { + goto invalid; + } + + if (ch == '.' || ch == ' ') { + break; + } + + state = sw_normal; + break; + } + } + + if (state == sw_after_dot) { + goto invalid; + } + + if (dirname && slash) { + ch = *slash; + *slash = '\0'; + len = slash - u + 1; + } + + /* check if long name match */ + + lu = malloc(len * 2); + if (lu == NULL) { + return NGX_ERROR; + } + + n = GetLongPathNameW(u, lu, len); + + if (n == 0) { + + if (dirname && slash && ngx_errno == NGX_ENOENT) { + ngx_set_errno(NGX_ENOPATH); + } + + goto failed; + } + + if (n != len - 1 || _wcsicmp(u, lu) != 0) { + goto invalid; + } + + if (dirname && slash) { + *slash = ch; + } + + ngx_free(lu); + + return NGX_OK; + +invalid: + + ngx_set_errno(NGX_ENOENT); + +failed: + + if (dirname && slash) { + *slash = ch; + } + + if (lu) { + err = ngx_errno; + ngx_free(lu); + ngx_set_errno(err); + } + + return NGX_ERROR; +} + + +static u_short * +ngx_utf8_to_utf16(u_short *utf16, u_char *utf8, size_t *len, size_t reserved) +{ + u_char *p; + u_short *u, *last; + uint32_t n; + + p = utf8; + u = utf16; + last = utf16 + *len; + + while (u < last) { + + if (*p < 0x80) { + *u++ = (u_short) *p; + + if (*p == 0) { + *len = u - utf16; + return utf16; + } + + p++; + + continue; + } + + if (u + 1 == last) { + *len = u - utf16; + break; + } + + n = ngx_utf8_decode(&p, 4); + + if (n > 0x10ffff) { + ngx_set_errno(NGX_EILSEQ); + return NULL; + } + + if (n > 0xffff) { + n -= 0x10000; + *u++ = (u_short) (0xd800 + (n >> 10)); + *u++ = (u_short) (0xdc00 + (n & 0x03ff)); + continue; + } + + *u++ = (u_short) n; + } + + /* the given buffer is not enough, allocate a new one */ + + u = malloc(((p - utf8) + ngx_strlen(p) + 1 + reserved) * sizeof(u_short)); + if (u == NULL) { + return NULL; + } + + ngx_memcpy(u, utf16, *len * 2); + + utf16 = u; + u += *len; + + for ( ;; ) { + + if (*p < 0x80) { + *u++ = (u_short) *p; + + if (*p == 0) { + *len = u - utf16; + return utf16; + } + + p++; + + continue; + } + + n = ngx_utf8_decode(&p, 4); + + if (n > 0x10ffff) { + ngx_free(utf16); + ngx_set_errno(NGX_EILSEQ); + return NULL; + } + + if (n > 0xffff) { + n -= 0x10000; + *u++ = (u_short) (0xd800 + (n >> 10)); + *u++ = (u_short) (0xdc00 + (n & 0x03ff)); + continue; + } + + *u++ = (u_short) n; + } + + /* unreachable */ +} + + +static u_char * +ngx_utf16_to_utf8(u_char *utf8, u_short *utf16, size_t *len, size_t *allocated) +{ + u_char *p, *last; + u_short *u, *j; + uint32_t n; + + u = utf16; + p = utf8; + last = utf8 + *len; + + while (p < last) { + + if (*u < 0x80) { + *p++ = (u_char) *u; + + if (*u == 0) { + *len = p - utf8; + return utf8; + } + + u++; + + continue; + } + + if (p >= last - 4) { + *len = p - utf8; + break; + } + + n = ngx_utf16_decode(&u, 2); + + if (n > 0x10ffff) { + ngx_set_errno(NGX_EILSEQ); + return NULL; + } + + if (n >= 0x10000) { + *p++ = (u_char) (0xf0 + (n >> 18)); + *p++ = (u_char) (0x80 + ((n >> 12) & 0x3f)); + *p++ = (u_char) (0x80 + ((n >> 6) & 0x3f)); + *p++ = (u_char) (0x80 + (n & 0x3f)); + continue; + } + + if (n >= 0x0800) { + *p++ = (u_char) (0xe0 + (n >> 12)); + *p++ = (u_char) (0x80 + ((n >> 6) & 0x3f)); + *p++ = (u_char) (0x80 + (n & 0x3f)); + continue; + } + + *p++ = (u_char) (0xc0 + (n >> 6)); + *p++ = (u_char) (0x80 + (n & 0x3f)); + } + + /* the given buffer is not enough, allocate a new one */ + + for (j = u; *j; j++) { /* void */ } + + p = malloc((j - utf16) * 4 + 1); + if (p == NULL) { + return NULL; + } + + if (allocated) { + *allocated = (j - utf16) * 4 + 1; + } + + ngx_memcpy(p, utf8, *len); + + utf8 = p; + p += *len; + + for ( ;; ) { + + if (*u < 0x80) { + *p++ = (u_char) *u; + + if (*u == 0) { + *len = p - utf8; + return utf8; + } + + u++; + + continue; + } + + n = ngx_utf16_decode(&u, 2); + + if (n > 0x10ffff) { + ngx_free(utf8); + ngx_set_errno(NGX_EILSEQ); + return NULL; + } + + if (n >= 0x10000) { + *p++ = (u_char) (0xf0 + (n >> 18)); + *p++ = (u_char) (0x80 + ((n >> 12) & 0x3f)); + *p++ = (u_char) (0x80 + ((n >> 6) & 0x3f)); + *p++ = (u_char) (0x80 + (n & 0x3f)); + continue; + } + + if (n >= 0x0800) { + *p++ = (u_char) (0xe0 + (n >> 12)); + *p++ = (u_char) (0x80 + ((n >> 6) & 0x3f)); + *p++ = (u_char) (0x80 + (n & 0x3f)); + continue; + } + + *p++ = (u_char) (0xc0 + (n >> 6)); + *p++ = (u_char) (0x80 + (n & 0x3f)); + } + + /* unreachable */ +} + + +/* + * ngx_utf16_decode() decodes one or two UTF-16 code units + * the return values: + * 0x80 - 0x10ffff valid character + * 0x110000 - 0xfffffffd invalid sequence + * 0xfffffffe incomplete sequence + * 0xffffffff error + */ + +uint32_t +ngx_utf16_decode(u_short **u, size_t n) +{ + uint32_t k, m; + + k = **u; + + if (k < 0xd800 || k > 0xdfff) { + (*u)++; + return k; + } + + if (k > 0xdbff) { + (*u)++; + return 0xffffffff; + } + + if (n < 2) { + return 0xfffffffe; + } + + (*u)++; + + m = *(*u)++; + + if (m < 0xdc00 || m > 0xdfff) { + return 0xffffffff; + + } + + return 0x10000 + ((k - 0xd800) << 10) + (m - 0xdc00); +} diff --git a/src/os/win32/ngx_files.h b/src/os/win32/ngx_files.h new file mode 100644 index 0000000..78ba13a --- /dev/null +++ b/src/os/win32/ngx_files.h @@ -0,0 +1,272 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_FILES_H_INCLUDED_ +#define _NGX_FILES_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +typedef HANDLE ngx_fd_t; +typedef BY_HANDLE_FILE_INFORMATION ngx_file_info_t; +typedef uint64_t ngx_file_uniq_t; + + +typedef struct { + u_char *name; + size_t size; + void *addr; + ngx_fd_t fd; + HANDLE handle; + ngx_log_t *log; +} ngx_file_mapping_t; + + +typedef struct { + HANDLE dir; + WIN32_FIND_DATAW finddata; + + u_char *name; + size_t namelen; + size_t allocated; + + unsigned valid_info:1; + unsigned type:1; + unsigned ready:1; +} ngx_dir_t; + + +typedef struct { + HANDLE dir; + WIN32_FIND_DATAW finddata; + + unsigned ready:1; + unsigned test:1; + unsigned no_match:1; + + u_char *pattern; + ngx_str_t name; + size_t last; + ngx_log_t *log; +} ngx_glob_t; + + + +/* INVALID_FILE_ATTRIBUTES is specified but not defined at least in MSVC6SP2 */ +#ifndef INVALID_FILE_ATTRIBUTES +#define INVALID_FILE_ATTRIBUTES 0xffffffff +#endif + +/* INVALID_SET_FILE_POINTER is not defined at least in MSVC6SP2 */ +#ifndef INVALID_SET_FILE_POINTER +#define INVALID_SET_FILE_POINTER 0xffffffff +#endif + + +#define NGX_INVALID_FILE INVALID_HANDLE_VALUE +#define NGX_FILE_ERROR 0 + + +ngx_fd_t ngx_open_file(u_char *name, u_long mode, u_long create, u_long access); +#define ngx_open_file_n "CreateFile()" + +#define NGX_FILE_RDONLY GENERIC_READ +#define NGX_FILE_WRONLY GENERIC_WRITE +#define NGX_FILE_RDWR GENERIC_READ|GENERIC_WRITE +#define NGX_FILE_APPEND FILE_APPEND_DATA|SYNCHRONIZE +#define NGX_FILE_NONBLOCK 0 + +#define NGX_FILE_CREATE_OR_OPEN OPEN_ALWAYS +#define NGX_FILE_OPEN OPEN_EXISTING +#define NGX_FILE_TRUNCATE CREATE_ALWAYS + +#define NGX_FILE_DEFAULT_ACCESS 0 +#define NGX_FILE_OWNER_ACCESS 0 + + +ngx_fd_t ngx_open_tempfile(u_char *name, ngx_uint_t persistent, + ngx_uint_t access); +#define ngx_open_tempfile_n "CreateFile()" + + +#define ngx_close_file CloseHandle +#define ngx_close_file_n "CloseHandle()" + + +ssize_t ngx_read_fd(ngx_fd_t fd, void *buf, size_t size); +#define ngx_read_fd_n "ReadFile()" + + +ssize_t ngx_write_fd(ngx_fd_t fd, void *buf, size_t size); +#define ngx_write_fd_n "WriteFile()" + + +ssize_t ngx_write_console(ngx_fd_t fd, void *buf, size_t size); + + +#define ngx_linefeed(p) *p++ = CR; *p++ = LF; +#define NGX_LINEFEED_SIZE 2 +#define NGX_LINEFEED CRLF + + +ngx_int_t ngx_delete_file(u_char *name); +#define ngx_delete_file_n "DeleteFile()" + + +ngx_int_t ngx_rename_file(u_char *from, u_char *to); +#define ngx_rename_file_n "MoveFile()" +ngx_err_t ngx_win32_rename_file(ngx_str_t *from, ngx_str_t *to, ngx_log_t *log); + + + +ngx_int_t ngx_set_file_time(u_char *name, ngx_fd_t fd, time_t s); +#define ngx_set_file_time_n "SetFileTime()" + + +ngx_int_t ngx_file_info(u_char *filename, ngx_file_info_t *fi); +#define ngx_file_info_n "GetFileAttributesEx()" + + +#define ngx_fd_info(fd, fi) GetFileInformationByHandle(fd, fi) +#define ngx_fd_info_n "GetFileInformationByHandle()" + + +#define ngx_link_info(name, fi) ngx_file_info(name, fi) +#define ngx_link_info_n "GetFileAttributesEx()" + + +#define ngx_is_dir(fi) \ + (((fi)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) +#define ngx_is_file(fi) \ + (((fi)->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) +#define ngx_is_link(fi) 0 +#define ngx_is_exec(fi) 0 + +#define ngx_file_access(fi) 0 + +#define ngx_file_size(fi) \ + (((off_t) (fi)->nFileSizeHigh << 32) | (fi)->nFileSizeLow) +#define ngx_file_fs_size(fi) ngx_file_size(fi) + +#define ngx_file_uniq(fi) \ + (((ngx_file_uniq_t) (fi)->nFileIndexHigh << 32) | (fi)->nFileIndexLow) + + +/* 116444736000000000 is commented in src/os/win32/ngx_time.c */ + +#define ngx_file_mtime(fi) \ + (time_t) (((((unsigned __int64) (fi)->ftLastWriteTime.dwHighDateTime << 32) \ + | (fi)->ftLastWriteTime.dwLowDateTime) \ + - 116444736000000000) / 10000000) + +ngx_int_t ngx_create_file_mapping(ngx_file_mapping_t *fm); +void ngx_close_file_mapping(ngx_file_mapping_t *fm); + + +u_char *ngx_realpath(u_char *path, u_char *resolved); +#define ngx_realpath_n "" + + +size_t ngx_getcwd(u_char *buf, size_t size); +#define ngx_getcwd_n "GetCurrentDirectory()" + + +#define ngx_path_separator(c) ((c) == '/' || (c) == '\\') + +#define NGX_HAVE_MAX_PATH 1 +#define NGX_MAX_PATH MAX_PATH + + +ngx_int_t ngx_open_dir(ngx_str_t *name, ngx_dir_t *dir); +#define ngx_open_dir_n "FindFirstFile()" + + +ngx_int_t ngx_read_dir(ngx_dir_t *dir); +#define ngx_read_dir_n "FindNextFile()" + + +ngx_int_t ngx_close_dir(ngx_dir_t *dir); +#define ngx_close_dir_n "FindClose()" + + +ngx_int_t ngx_create_dir(u_char *name, ngx_uint_t access); +#define ngx_create_dir_n "CreateDirectory()" + + +ngx_int_t ngx_delete_dir(u_char *name); +#define ngx_delete_dir_n "RemoveDirectory()" + + +#define ngx_dir_access(a) (a) + + +#define ngx_de_name(dir) (dir)->name +#define ngx_de_namelen(dir) (dir)->namelen + +ngx_int_t ngx_de_info(u_char *name, ngx_dir_t *dir); +#define ngx_de_info_n "dummy()" + +ngx_int_t ngx_de_link_info(u_char *name, ngx_dir_t *dir); +#define ngx_de_link_info_n "dummy()" + +#define ngx_de_is_dir(dir) \ + (((dir)->finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) +#define ngx_de_is_file(dir) \ + (((dir)->finddata.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) +#define ngx_de_is_link(dir) 0 +#define ngx_de_access(dir) 0 +#define ngx_de_size(dir) \ + (((off_t) (dir)->finddata.nFileSizeHigh << 32) | (dir)->finddata.nFileSizeLow) +#define ngx_de_fs_size(dir) ngx_de_size(dir) + +/* 116444736000000000 is commented in src/os/win32/ngx_time.c */ + +#define ngx_de_mtime(dir) \ + (time_t) (((((unsigned __int64) \ + (dir)->finddata.ftLastWriteTime.dwHighDateTime << 32) \ + | (dir)->finddata.ftLastWriteTime.dwLowDateTime) \ + - 116444736000000000) / 10000000) + + +ngx_int_t ngx_open_glob(ngx_glob_t *gl); +#define ngx_open_glob_n "FindFirstFile()" + +ngx_int_t ngx_read_glob(ngx_glob_t *gl, ngx_str_t *name); +void ngx_close_glob(ngx_glob_t *gl); + + +ssize_t ngx_read_file(ngx_file_t *file, u_char *buf, size_t size, off_t offset); +#define ngx_read_file_n "ReadFile()" + +ssize_t ngx_write_file(ngx_file_t *file, u_char *buf, size_t size, + off_t offset); + +ssize_t ngx_write_chain_to_file(ngx_file_t *file, ngx_chain_t *ce, + off_t offset, ngx_pool_t *pool); + +ngx_int_t ngx_read_ahead(ngx_fd_t fd, size_t n); +#define ngx_read_ahead_n "ngx_read_ahead_n" + +ngx_int_t ngx_directio_on(ngx_fd_t fd); +#define ngx_directio_on_n "ngx_directio_on_n" + +ngx_int_t ngx_directio_off(ngx_fd_t fd); +#define ngx_directio_off_n "ngx_directio_off_n" + +size_t ngx_fs_bsize(u_char *name); +off_t ngx_fs_available(u_char *name); + + +#define ngx_stdout GetStdHandle(STD_OUTPUT_HANDLE) +#define ngx_stderr GetStdHandle(STD_ERROR_HANDLE) +#define ngx_set_stderr(fd) SetStdHandle(STD_ERROR_HANDLE, fd) +#define ngx_set_stderr_n "SetStdHandle(STD_ERROR_HANDLE)" + + +#endif /* _NGX_FILES_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_os.h b/src/os/win32/ngx_os.h new file mode 100644 index 0000000..15f5aa0 --- /dev/null +++ b/src/os/win32/ngx_os.h @@ -0,0 +1,68 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_OS_H_INCLUDED_ +#define _NGX_OS_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +#define NGX_IO_SENDFILE 1 + + +typedef ssize_t (*ngx_recv_pt)(ngx_connection_t *c, u_char *buf, size_t size); +typedef ssize_t (*ngx_recv_chain_pt)(ngx_connection_t *c, ngx_chain_t *in, + off_t limit); +typedef ssize_t (*ngx_send_pt)(ngx_connection_t *c, u_char *buf, size_t size); +typedef ngx_chain_t *(*ngx_send_chain_pt)(ngx_connection_t *c, ngx_chain_t *in, + off_t limit); + +typedef struct { + ngx_recv_pt recv; + ngx_recv_chain_pt recv_chain; + ngx_recv_pt udp_recv; + ngx_send_pt send; + ngx_send_pt udp_send; + ngx_send_chain_pt udp_send_chain; + ngx_send_chain_pt send_chain; + ngx_uint_t flags; +} ngx_os_io_t; + + +ngx_int_t ngx_os_init(ngx_log_t *log); +void ngx_os_status(ngx_log_t *log); +ngx_int_t ngx_os_signal_process(ngx_cycle_t *cycle, char *sig, ngx_pid_t pid); + +ssize_t ngx_wsarecv(ngx_connection_t *c, u_char *buf, size_t size); +ssize_t ngx_overlapped_wsarecv(ngx_connection_t *c, u_char *buf, size_t size); +ssize_t ngx_udp_wsarecv(ngx_connection_t *c, u_char *buf, size_t size); +ssize_t ngx_udp_overlapped_wsarecv(ngx_connection_t *c, u_char *buf, + size_t size); +ssize_t ngx_wsarecv_chain(ngx_connection_t *c, ngx_chain_t *chain, off_t limit); +ssize_t ngx_wsasend(ngx_connection_t *c, u_char *buf, size_t size); +ssize_t ngx_overlapped_wsasend(ngx_connection_t *c, u_char *buf, size_t size); +ngx_chain_t *ngx_wsasend_chain(ngx_connection_t *c, ngx_chain_t *in, + off_t limit); +ngx_chain_t *ngx_overlapped_wsasend_chain(ngx_connection_t *c, ngx_chain_t *in, + off_t limit); + +void ngx_cdecl ngx_event_log(ngx_err_t err, const char *fmt, ...); + + +extern ngx_os_io_t ngx_os_io; +extern ngx_uint_t ngx_ncpu; +extern ngx_uint_t ngx_max_wsabufs; +extern ngx_int_t ngx_max_sockets; +extern ngx_uint_t ngx_inherited_nonblocking; +extern ngx_uint_t ngx_tcp_nodelay_and_tcp_nopush; +extern ngx_uint_t ngx_win32_version; +extern char ngx_unique[]; + + +#endif /* _NGX_OS_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_process.c b/src/os/win32/ngx_process.c new file mode 100644 index 0000000..57b1ae9 --- /dev/null +++ b/src/os/win32/ngx_process.c @@ -0,0 +1,238 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +int ngx_argc; +char **ngx_argv; +char **ngx_os_argv; + +ngx_int_t ngx_last_process; +ngx_process_t ngx_processes[NGX_MAX_PROCESSES]; + + +ngx_pid_t +ngx_spawn_process(ngx_cycle_t *cycle, char *name, ngx_int_t respawn) +{ + u_long rc, n, code; + ngx_int_t s; + ngx_pid_t pid; + ngx_exec_ctx_t ctx; + HANDLE events[2]; + char file[MAX_PATH + 1]; + + if (respawn >= 0) { + s = respawn; + + } else { + for (s = 0; s < ngx_last_process; s++) { + if (ngx_processes[s].handle == NULL) { + break; + } + } + + if (s == NGX_MAX_PROCESSES) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "no more than %d processes can be spawned", + NGX_MAX_PROCESSES); + return NGX_INVALID_PID; + } + } + + n = GetModuleFileName(NULL, file, MAX_PATH); + + if (n == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "GetModuleFileName() failed"); + return NGX_INVALID_PID; + } + + file[n] = '\0'; + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "GetModuleFileName: \"%s\"", file); + + ctx.path = file; + ctx.name = name; + ctx.args = GetCommandLine(); + ctx.argv = NULL; + ctx.envp = NULL; + + pid = ngx_execute(cycle, &ctx); + + if (pid == NGX_INVALID_PID) { + return pid; + } + + ngx_memzero(&ngx_processes[s], sizeof(ngx_process_t)); + + ngx_processes[s].handle = ctx.child; + ngx_processes[s].pid = pid; + ngx_processes[s].name = name; + + ngx_sprintf(ngx_processes[s].term_event, "ngx_%s_term_%P%Z", name, pid); + ngx_sprintf(ngx_processes[s].quit_event, "ngx_%s_quit_%P%Z", name, pid); + ngx_sprintf(ngx_processes[s].reopen_event, "ngx_%s_reopen_%P%Z", + name, pid); + + events[0] = ngx_master_process_event; + events[1] = ctx.child; + + rc = WaitForMultipleObjects(2, events, 0, 5000); + + ngx_time_update(); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "WaitForMultipleObjects: %ul", rc); + + switch (rc) { + + case WAIT_OBJECT_0: + + ngx_processes[s].term = OpenEvent(EVENT_MODIFY_STATE, 0, + (char *) ngx_processes[s].term_event); + if (ngx_processes[s].term == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "OpenEvent(\"%s\") failed", + ngx_processes[s].term_event); + goto failed; + } + + ngx_processes[s].quit = OpenEvent(EVENT_MODIFY_STATE, 0, + (char *) ngx_processes[s].quit_event); + if (ngx_processes[s].quit == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "OpenEvent(\"%s\") failed", + ngx_processes[s].quit_event); + goto failed; + } + + ngx_processes[s].reopen = OpenEvent(EVENT_MODIFY_STATE, 0, + (char *) ngx_processes[s].reopen_event); + if (ngx_processes[s].reopen == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "OpenEvent(\"%s\") failed", + ngx_processes[s].reopen_event); + goto failed; + } + + if (ResetEvent(ngx_master_process_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "ResetEvent(\"%s\") failed", + ngx_master_process_event_name); + goto failed; + } + + break; + + case WAIT_OBJECT_0 + 1: + if (GetExitCodeProcess(ctx.child, &code) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "GetExitCodeProcess(%P) failed", pid); + } + + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "%s process %P exited with code %Xl", + name, pid, code); + + goto failed; + + case WAIT_TIMEOUT: + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "the event \"%s\" was not signaled for 5s", + ngx_master_process_event_name); + goto failed; + + case WAIT_FAILED: + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "WaitForSingleObject(\"%s\") failed", + ngx_master_process_event_name); + + goto failed; + } + + if (respawn >= 0) { + return pid; + } + + switch (respawn) { + + case NGX_PROCESS_RESPAWN: + ngx_processes[s].just_spawn = 0; + break; + + case NGX_PROCESS_JUST_RESPAWN: + ngx_processes[s].just_spawn = 1; + break; + } + + if (s == ngx_last_process) { + ngx_last_process++; + } + + return pid; + +failed: + + if (ngx_processes[s].reopen) { + ngx_close_handle(ngx_processes[s].reopen); + } + + if (ngx_processes[s].quit) { + ngx_close_handle(ngx_processes[s].quit); + } + + if (ngx_processes[s].term) { + ngx_close_handle(ngx_processes[s].term); + } + + TerminateProcess(ngx_processes[s].handle, 2); + + if (ngx_processes[s].handle) { + ngx_close_handle(ngx_processes[s].handle); + ngx_processes[s].handle = NULL; + } + + return NGX_INVALID_PID; +} + + +ngx_pid_t +ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx) +{ + STARTUPINFO si; + PROCESS_INFORMATION pi; + + ngx_memzero(&si, sizeof(STARTUPINFO)); + si.cb = sizeof(STARTUPINFO); + + ngx_memzero(&pi, sizeof(PROCESS_INFORMATION)); + + if (CreateProcess(ctx->path, ctx->args, + NULL, NULL, 0, CREATE_NO_WINDOW, NULL, NULL, &si, &pi) + == 0) + { + ngx_log_error(NGX_LOG_CRIT, cycle->log, ngx_errno, + "CreateProcess(\"%s\") failed", ngx_argv[0]); + + return 0; + } + + ctx->child = pi.hProcess; + + if (CloseHandle(pi.hThread) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CloseHandle(pi.hThread) failed"); + } + + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, + "start %s process %P", ctx->name, pi.dwProcessId); + + return pi.dwProcessId; +} diff --git a/src/os/win32/ngx_process.h b/src/os/win32/ngx_process.h new file mode 100644 index 0000000..7ec4cd9 --- /dev/null +++ b/src/os/win32/ngx_process.h @@ -0,0 +1,80 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_PROCESS_H_INCLUDED_ +#define _NGX_PROCESS_H_INCLUDED_ + + +typedef DWORD ngx_pid_t; +#define NGX_INVALID_PID 0 + + +#define ngx_getpid GetCurrentProcessId +#define ngx_getppid() 0 +#define ngx_log_pid ngx_pid + + +#define NGX_PROCESS_SYNC_NAME \ + (sizeof("ngx_cache_manager_mutex_") + NGX_INT32_LEN) + + +typedef uint64_t ngx_cpuset_t; + + +typedef struct { + HANDLE handle; + ngx_pid_t pid; + char *name; + + HANDLE term; + HANDLE quit; + HANDLE reopen; + + u_char term_event[NGX_PROCESS_SYNC_NAME]; + u_char quit_event[NGX_PROCESS_SYNC_NAME]; + u_char reopen_event[NGX_PROCESS_SYNC_NAME]; + + unsigned just_spawn:1; + unsigned exiting:1; +} ngx_process_t; + + +typedef struct { + char *path; + char *name; + char *args; + char *const *argv; + char *const *envp; + HANDLE child; +} ngx_exec_ctx_t; + + +ngx_pid_t ngx_spawn_process(ngx_cycle_t *cycle, char *name, ngx_int_t respawn); +ngx_pid_t ngx_execute(ngx_cycle_t *cycle, ngx_exec_ctx_t *ctx); + +#define ngx_debug_point() +#define ngx_sched_yield() SwitchToThread() + + +#define NGX_MAX_PROCESSES (MAXIMUM_WAIT_OBJECTS - 4) + +#define NGX_PROCESS_RESPAWN -2 +#define NGX_PROCESS_JUST_RESPAWN -3 + + +extern int ngx_argc; +extern char **ngx_argv; +extern char **ngx_os_argv; + +extern ngx_int_t ngx_last_process; +extern ngx_process_t ngx_processes[NGX_MAX_PROCESSES]; + +extern ngx_pid_t ngx_pid; +extern ngx_pid_t ngx_parent; + + +#endif /* _NGX_PROCESS_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_process_cycle.c b/src/os/win32/ngx_process_cycle.c new file mode 100644 index 0000000..a39335f --- /dev/null +++ b/src/os/win32/ngx_process_cycle.c @@ -0,0 +1,1044 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> +#include <nginx.h> + + +static void ngx_console_init(ngx_cycle_t *cycle); +static int __stdcall ngx_console_handler(u_long type); +static ngx_int_t ngx_create_signal_events(ngx_cycle_t *cycle); +static ngx_int_t ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t type); +static void ngx_reopen_worker_processes(ngx_cycle_t *cycle); +static void ngx_quit_worker_processes(ngx_cycle_t *cycle, ngx_uint_t old); +static void ngx_terminate_worker_processes(ngx_cycle_t *cycle); +static ngx_uint_t ngx_reap_worker(ngx_cycle_t *cycle, HANDLE h); +static void ngx_master_process_exit(ngx_cycle_t *cycle); +static void ngx_worker_process_cycle(ngx_cycle_t *cycle, char *mevn); +static void ngx_worker_process_exit(ngx_cycle_t *cycle); +static ngx_thread_value_t __stdcall ngx_worker_thread(void *data); +static ngx_thread_value_t __stdcall ngx_cache_manager_thread(void *data); +static void ngx_cache_manager_process_handler(void); +static ngx_thread_value_t __stdcall ngx_cache_loader_thread(void *data); + + +ngx_uint_t ngx_process; +ngx_uint_t ngx_worker; +ngx_pid_t ngx_pid; +ngx_pid_t ngx_parent; + +ngx_uint_t ngx_inherited; +ngx_pid_t ngx_new_binary; + +sig_atomic_t ngx_terminate; +sig_atomic_t ngx_quit; +sig_atomic_t ngx_reopen; +sig_atomic_t ngx_reconfigure; +ngx_uint_t ngx_exiting; + + +HANDLE ngx_master_process_event; +char ngx_master_process_event_name[NGX_PROCESS_SYNC_NAME]; + +static HANDLE ngx_stop_event; +static char ngx_stop_event_name[NGX_PROCESS_SYNC_NAME]; +static HANDLE ngx_quit_event; +static char ngx_quit_event_name[NGX_PROCESS_SYNC_NAME]; +static HANDLE ngx_reopen_event; +static char ngx_reopen_event_name[NGX_PROCESS_SYNC_NAME]; +static HANDLE ngx_reload_event; +static char ngx_reload_event_name[NGX_PROCESS_SYNC_NAME]; + +HANDLE ngx_cache_manager_mutex; +char ngx_cache_manager_mutex_name[NGX_PROCESS_SYNC_NAME]; +HANDLE ngx_cache_manager_event; + + +void +ngx_master_process_cycle(ngx_cycle_t *cycle) +{ + u_long nev, ev, timeout; + ngx_err_t err; + ngx_int_t n; + ngx_msec_t timer; + ngx_uint_t live; + HANDLE events[MAXIMUM_WAIT_OBJECTS]; + + ngx_sprintf((u_char *) ngx_master_process_event_name, + "ngx_master_%s%Z", ngx_unique); + + if (ngx_process == NGX_PROCESS_WORKER) { + ngx_worker_process_cycle(cycle, ngx_master_process_event_name); + return; + } + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, cycle->log, 0, "master started"); + + ngx_console_init(cycle); + + SetEnvironmentVariable("ngx_unique", ngx_unique); + + ngx_master_process_event = CreateEvent(NULL, 1, 0, + ngx_master_process_event_name); + if (ngx_master_process_event == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateEvent(\"%s\") failed", + ngx_master_process_event_name); + exit(2); + } + + if (ngx_create_signal_events(cycle) != NGX_OK) { + exit(2); + } + + ngx_sprintf((u_char *) ngx_cache_manager_mutex_name, + "ngx_cache_manager_mutex_%s%Z", ngx_unique); + + ngx_cache_manager_mutex = CreateMutex(NULL, 0, + ngx_cache_manager_mutex_name); + if (ngx_cache_manager_mutex == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateMutex(\"%s\") failed", ngx_cache_manager_mutex_name); + exit(2); + } + + + events[0] = ngx_stop_event; + events[1] = ngx_quit_event; + events[2] = ngx_reopen_event; + events[3] = ngx_reload_event; + + ngx_close_listening_sockets(cycle); + + if (ngx_start_worker_processes(cycle, NGX_PROCESS_RESPAWN) == 0) { + exit(2); + } + + timer = 0; + timeout = INFINITE; + + for ( ;; ) { + + nev = 4; + for (n = 0; n < ngx_last_process; n++) { + if (ngx_processes[n].handle) { + events[nev++] = ngx_processes[n].handle; + } + } + + if (timer) { + timeout = timer > ngx_current_msec ? timer - ngx_current_msec : 0; + } + + ev = WaitForMultipleObjects(nev, events, 0, timeout); + + err = ngx_errno; + ngx_time_update(); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "master WaitForMultipleObjects: %ul", ev); + + if (ev == WAIT_OBJECT_0) { + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting"); + + if (ResetEvent(ngx_stop_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "ResetEvent(\"%s\") failed", ngx_stop_event_name); + } + + if (timer == 0) { + timer = ngx_current_msec + 5000; + } + + ngx_terminate = 1; + ngx_quit_worker_processes(cycle, 0); + + continue; + } + + if (ev == WAIT_OBJECT_0 + 1) { + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "shutting down"); + + if (ResetEvent(ngx_quit_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "ResetEvent(\"%s\") failed", ngx_quit_event_name); + } + + ngx_quit = 1; + ngx_quit_worker_processes(cycle, 0); + + continue; + } + + if (ev == WAIT_OBJECT_0 + 2) { + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reopening logs"); + + if (ResetEvent(ngx_reopen_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "ResetEvent(\"%s\") failed", + ngx_reopen_event_name); + } + + ngx_reopen_files(cycle, -1); + ngx_reopen_worker_processes(cycle); + + continue; + } + + if (ev == WAIT_OBJECT_0 + 3) { + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "reconfiguring"); + + if (ResetEvent(ngx_reload_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "ResetEvent(\"%s\") failed", + ngx_reload_event_name); + } + + cycle = ngx_init_cycle(cycle); + if (cycle == NULL) { + cycle = (ngx_cycle_t *) ngx_cycle; + continue; + } + + ngx_cycle = cycle; + + ngx_close_listening_sockets(cycle); + + if (ngx_start_worker_processes(cycle, NGX_PROCESS_JUST_RESPAWN)) { + ngx_quit_worker_processes(cycle, 1); + } + + continue; + } + + if (ev > WAIT_OBJECT_0 + 3 && ev < WAIT_OBJECT_0 + nev) { + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, cycle->log, 0, "reap worker"); + + live = ngx_reap_worker(cycle, events[ev]); + + if (!live && (ngx_terminate || ngx_quit)) { + ngx_master_process_exit(cycle); + } + + continue; + } + + if (ev == WAIT_TIMEOUT) { + ngx_terminate_worker_processes(cycle); + + ngx_master_process_exit(cycle); + } + + if (ev == WAIT_FAILED) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, err, + "WaitForMultipleObjects() failed"); + + continue; + } + + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "WaitForMultipleObjects() returned unexpected value %ul", ev); + } +} + + +static void +ngx_console_init(ngx_cycle_t *cycle) +{ + ngx_core_conf_t *ccf; + + ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); + + if (ccf->daemon) { + if (FreeConsole() == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "FreeConsole() failed"); + } + + return; + } + + if (SetConsoleCtrlHandler(ngx_console_handler, 1) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "SetConsoleCtrlHandler() failed"); + } +} + + +static int __stdcall +ngx_console_handler(u_long type) +{ + char *msg; + + switch (type) { + + case CTRL_C_EVENT: + msg = "Ctrl-C pressed, exiting"; + break; + + case CTRL_BREAK_EVENT: + msg = "Ctrl-Break pressed, exiting"; + break; + + case CTRL_CLOSE_EVENT: + msg = "console closing, exiting"; + break; + + case CTRL_LOGOFF_EVENT: + msg = "user logs off, exiting"; + break; + + default: + return 0; + } + + ngx_log_error(NGX_LOG_NOTICE, ngx_cycle->log, 0, msg); + + if (ngx_stop_event == NULL) { + return 1; + } + + if (SetEvent(ngx_stop_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, 0, + "SetEvent(\"%s\") failed", ngx_stop_event_name); + } + + return 1; +} + + +static ngx_int_t +ngx_create_signal_events(ngx_cycle_t *cycle) +{ + ngx_sprintf((u_char *) ngx_stop_event_name, + "Global\\ngx_stop_%s%Z", ngx_unique); + + ngx_stop_event = CreateEvent(NULL, 1, 0, ngx_stop_event_name); + if (ngx_stop_event == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateEvent(\"%s\") failed", ngx_stop_event_name); + return NGX_ERROR; + } + + + ngx_sprintf((u_char *) ngx_quit_event_name, + "Global\\ngx_quit_%s%Z", ngx_unique); + + ngx_quit_event = CreateEvent(NULL, 1, 0, ngx_quit_event_name); + if (ngx_quit_event == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateEvent(\"%s\") failed", ngx_quit_event_name); + return NGX_ERROR; + } + + + ngx_sprintf((u_char *) ngx_reopen_event_name, + "Global\\ngx_reopen_%s%Z", ngx_unique); + + ngx_reopen_event = CreateEvent(NULL, 1, 0, ngx_reopen_event_name); + if (ngx_reopen_event == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateEvent(\"%s\") failed", ngx_reopen_event_name); + return NGX_ERROR; + } + + + ngx_sprintf((u_char *) ngx_reload_event_name, + "Global\\ngx_reload_%s%Z", ngx_unique); + + ngx_reload_event = CreateEvent(NULL, 1, 0, ngx_reload_event_name); + if (ngx_reload_event == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateEvent(\"%s\") failed", ngx_reload_event_name); + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_start_worker_processes(ngx_cycle_t *cycle, ngx_int_t type) +{ + ngx_int_t n; + ngx_core_conf_t *ccf; + + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "start worker processes"); + + ccf = (ngx_core_conf_t *) ngx_get_conf(cycle->conf_ctx, ngx_core_module); + + for (n = 0; n < ccf->worker_processes; n++) { + if (ngx_spawn_process(cycle, "worker", type) == NGX_INVALID_PID) { + break; + } + } + + return n; +} + + +static void +ngx_reopen_worker_processes(ngx_cycle_t *cycle) +{ + ngx_int_t n; + + for (n = 0; n < ngx_last_process; n++) { + + if (ngx_processes[n].handle == NULL) { + continue; + } + + if (SetEvent(ngx_processes[n].reopen) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "SetEvent(\"%s\") failed", + ngx_processes[n].reopen_event); + } + } +} + + +static void +ngx_quit_worker_processes(ngx_cycle_t *cycle, ngx_uint_t old) +{ + ngx_int_t n; + + for (n = 0; n < ngx_last_process; n++) { + + ngx_log_debug5(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "process: %d %P %p e:%d j:%d", + n, + ngx_processes[n].pid, + ngx_processes[n].handle, + ngx_processes[n].exiting, + ngx_processes[n].just_spawn); + + if (old && ngx_processes[n].just_spawn) { + ngx_processes[n].just_spawn = 0; + continue; + } + + if (ngx_processes[n].handle == NULL) { + continue; + } + + if (SetEvent(ngx_processes[n].quit) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "SetEvent(\"%s\") failed", + ngx_processes[n].quit_event); + } + + ngx_processes[n].exiting = 1; + } +} + + +static void +ngx_terminate_worker_processes(ngx_cycle_t *cycle) +{ + ngx_int_t n; + + for (n = 0; n < ngx_last_process; n++) { + + if (ngx_processes[n].handle == NULL) { + continue; + } + + if (TerminateProcess(ngx_processes[n].handle, 0) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "TerminateProcess(\"%p\") failed", + ngx_processes[n].handle); + } + + ngx_processes[n].exiting = 1; + + ngx_close_handle(ngx_processes[n].reopen); + ngx_close_handle(ngx_processes[n].quit); + ngx_close_handle(ngx_processes[n].term); + ngx_close_handle(ngx_processes[n].handle); + } +} + + +static ngx_uint_t +ngx_reap_worker(ngx_cycle_t *cycle, HANDLE h) +{ + u_long code; + ngx_int_t n; + + for (n = 0; n < ngx_last_process; n++) { + + if (ngx_processes[n].handle != h) { + continue; + } + + if (GetExitCodeProcess(h, &code) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "GetExitCodeProcess(%P) failed", + ngx_processes[n].pid); + } + + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, + "%s process %P exited with code %Xl", + ngx_processes[n].name, ngx_processes[n].pid, code); + + ngx_close_handle(ngx_processes[n].reopen); + ngx_close_handle(ngx_processes[n].quit); + ngx_close_handle(ngx_processes[n].term); + ngx_close_handle(h); + + ngx_processes[n].handle = NULL; + ngx_processes[n].term = NULL; + ngx_processes[n].quit = NULL; + ngx_processes[n].reopen = NULL; + + if (!ngx_processes[n].exiting && !ngx_terminate && !ngx_quit) { + + if (ngx_spawn_process(cycle, ngx_processes[n].name, n) + == NGX_INVALID_PID) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "could not respawn %s", ngx_processes[n].name); + + if (n == ngx_last_process - 1) { + ngx_last_process--; + } + } + } + + goto found; + } + + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, "unknown process handle %p", h); + +found: + + for (n = 0; n < ngx_last_process; n++) { + + ngx_log_debug5(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "process: %d %P %p e:%d j:%d", + n, + ngx_processes[n].pid, + ngx_processes[n].handle, + ngx_processes[n].exiting, + ngx_processes[n].just_spawn); + + if (ngx_processes[n].handle) { + return 1; + } + } + + return 0; +} + + +static void +ngx_master_process_exit(ngx_cycle_t *cycle) +{ + ngx_uint_t i; + + ngx_delete_pidfile(cycle); + + ngx_close_handle(ngx_cache_manager_mutex); + ngx_close_handle(ngx_stop_event); + ngx_close_handle(ngx_quit_event); + ngx_close_handle(ngx_reopen_event); + ngx_close_handle(ngx_reload_event); + ngx_close_handle(ngx_master_process_event); + + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exit"); + + for (i = 0; cycle->modules[i]; i++) { + if (cycle->modules[i]->exit_master) { + cycle->modules[i]->exit_master(cycle); + } + } + + ngx_destroy_pool(cycle->pool); + + exit(0); +} + + +static void +ngx_worker_process_cycle(ngx_cycle_t *cycle, char *mevn) +{ + char wtevn[NGX_PROCESS_SYNC_NAME]; + char wqevn[NGX_PROCESS_SYNC_NAME]; + char wroevn[NGX_PROCESS_SYNC_NAME]; + HANDLE mev, events[3]; + u_long nev, ev; + ngx_err_t err; + ngx_tid_t wtid, cmtid, cltid; + ngx_log_t *log; + + log = cycle->log; + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, log, 0, "worker started"); + + ngx_sprintf((u_char *) wtevn, "ngx_worker_term_%P%Z", ngx_pid); + events[0] = CreateEvent(NULL, 1, 0, wtevn); + if (events[0] == NULL) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "CreateEvent(\"%s\") failed", wtevn); + goto failed; + } + + ngx_sprintf((u_char *) wqevn, "ngx_worker_quit_%P%Z", ngx_pid); + events[1] = CreateEvent(NULL, 1, 0, wqevn); + if (events[1] == NULL) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "CreateEvent(\"%s\") failed", wqevn); + goto failed; + } + + ngx_sprintf((u_char *) wroevn, "ngx_worker_reopen_%P%Z", ngx_pid); + events[2] = CreateEvent(NULL, 1, 0, wroevn); + if (events[2] == NULL) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "CreateEvent(\"%s\") failed", wroevn); + goto failed; + } + + mev = OpenEvent(EVENT_MODIFY_STATE, 0, mevn); + if (mev == NULL) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "OpenEvent(\"%s\") failed", mevn); + goto failed; + } + + if (SetEvent(mev) == 0) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "SetEvent(\"%s\") failed", mevn); + goto failed; + } + + + ngx_sprintf((u_char *) ngx_cache_manager_mutex_name, + "ngx_cache_manager_mutex_%s%Z", ngx_unique); + + ngx_cache_manager_mutex = OpenMutex(SYNCHRONIZE, 0, + ngx_cache_manager_mutex_name); + if (ngx_cache_manager_mutex == NULL) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "OpenMutex(\"%s\") failed", ngx_cache_manager_mutex_name); + goto failed; + } + + ngx_cache_manager_event = CreateEvent(NULL, 1, 0, NULL); + if (ngx_cache_manager_event == NULL) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "CreateEvent(\"ngx_cache_manager_event\") failed"); + goto failed; + } + + + if (ngx_create_thread(&wtid, ngx_worker_thread, NULL, log) != 0) { + goto failed; + } + + if (ngx_create_thread(&cmtid, ngx_cache_manager_thread, NULL, log) != 0) { + goto failed; + } + + if (ngx_create_thread(&cltid, ngx_cache_loader_thread, NULL, log) != 0) { + goto failed; + } + + for ( ;; ) { + ev = WaitForMultipleObjects(3, events, 0, INFINITE); + + err = ngx_errno; + ngx_time_update(); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "worker WaitForMultipleObjects: %ul", ev); + + if (ev == WAIT_OBJECT_0) { + ngx_terminate = 1; + ngx_log_error(NGX_LOG_NOTICE, log, 0, "exiting"); + + if (ResetEvent(events[0]) == 0) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + "ResetEvent(\"%s\") failed", wtevn); + } + + break; + } + + if (ev == WAIT_OBJECT_0 + 1) { + ngx_quit = 1; + ngx_log_error(NGX_LOG_NOTICE, log, 0, "gracefully shutting down"); + break; + } + + if (ev == WAIT_OBJECT_0 + 2) { + ngx_reopen = 1; + ngx_log_error(NGX_LOG_NOTICE, log, 0, "reopening logs"); + + if (ResetEvent(events[2]) == 0) { + ngx_log_error(NGX_LOG_ALERT, log, 0, + "ResetEvent(\"%s\") failed", wroevn); + } + + continue; + } + + if (ev == WAIT_FAILED) { + ngx_log_error(NGX_LOG_ALERT, log, err, + "WaitForMultipleObjects() failed"); + + goto failed; + } + } + + /* wait threads */ + + if (SetEvent(ngx_cache_manager_event) == 0) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_errno, + "SetEvent(\"ngx_cache_manager_event\") failed"); + } + + events[1] = wtid; + events[2] = cmtid; + + nev = 3; + + for ( ;; ) { + ev = WaitForMultipleObjects(nev, events, 0, INFINITE); + + err = ngx_errno; + ngx_time_update(); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, log, 0, + "worker exit WaitForMultipleObjects: %ul", ev); + + if (ev == WAIT_OBJECT_0) { + break; + } + + if (ev == WAIT_OBJECT_0 + 1) { + if (nev == 2) { + break; + } + + events[1] = events[2]; + nev = 2; + continue; + } + + if (ev == WAIT_OBJECT_0 + 2) { + nev = 2; + continue; + } + + if (ev == WAIT_FAILED) { + ngx_log_error(NGX_LOG_ALERT, log, err, + "WaitForMultipleObjects() failed"); + break; + } + } + + ngx_close_handle(ngx_cache_manager_event); + ngx_close_handle(events[0]); + ngx_close_handle(events[1]); + ngx_close_handle(events[2]); + ngx_close_handle(mev); + + ngx_worker_process_exit(cycle); + +failed: + + exit(2); +} + + +static ngx_thread_value_t __stdcall +ngx_worker_thread(void *data) +{ + ngx_int_t n; + ngx_time_t *tp; + ngx_cycle_t *cycle; + + tp = ngx_timeofday(); + srand((ngx_pid << 16) ^ (unsigned) tp->sec ^ tp->msec); + + cycle = (ngx_cycle_t *) ngx_cycle; + + for (n = 0; cycle->modules[n]; n++) { + if (cycle->modules[n]->init_process) { + if (cycle->modules[n]->init_process(cycle) == NGX_ERROR) { + /* fatal */ + exit(2); + } + } + } + + while (!ngx_quit) { + + if (ngx_exiting) { + if (ngx_event_no_timers_left() == NGX_OK) { + break; + } + } + + ngx_log_debug0(NGX_LOG_DEBUG_CORE, cycle->log, 0, "worker cycle"); + + ngx_process_events_and_timers(cycle); + + if (ngx_terminate) { + return 0; + } + + if (ngx_quit) { + ngx_quit = 0; + + if (!ngx_exiting) { + ngx_exiting = 1; + ngx_set_shutdown_timer(cycle); + ngx_close_listening_sockets(cycle); + ngx_close_idle_connections(cycle); + ngx_event_process_posted(cycle, &ngx_posted_events); + } + } + + if (ngx_reopen) { + ngx_reopen = 0; + ngx_reopen_files(cycle, -1); + } + } + + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting"); + + return 0; +} + + +static void +ngx_worker_process_exit(ngx_cycle_t *cycle) +{ + ngx_uint_t i; + ngx_connection_t *c; + + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exit"); + + for (i = 0; cycle->modules[i]; i++) { + if (cycle->modules[i]->exit_process) { + cycle->modules[i]->exit_process(cycle); + } + } + + if (ngx_exiting && !ngx_terminate) { + c = cycle->connections; + for (i = 0; i < cycle->connection_n; i++) { + if (c[i].fd != (ngx_socket_t) -1 + && c[i].read + && !c[i].read->accept + && !c[i].read->channel + && !c[i].read->resolver) + { + ngx_log_error(NGX_LOG_ALERT, cycle->log, 0, + "*%uA open socket #%d left in connection %ui", + c[i].number, c[i].fd, i); + } + } + } + + ngx_destroy_pool(cycle->pool); + + exit(0); +} + + +static ngx_thread_value_t __stdcall +ngx_cache_manager_thread(void *data) +{ + u_long ev; + HANDLE events[2]; + ngx_err_t err; + ngx_cycle_t *cycle; + + cycle = (ngx_cycle_t *) ngx_cycle; + + events[0] = ngx_cache_manager_event; + events[1] = ngx_cache_manager_mutex; + + for ( ;; ) { + ev = WaitForMultipleObjects(2, events, 0, INFINITE); + + err = ngx_errno; + ngx_time_update(); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, cycle->log, 0, + "cache manager WaitForMultipleObjects: %ul", ev); + + if (ev == WAIT_FAILED) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, err, + "WaitForMultipleObjects() failed"); + } + + /* + * ev == WAIT_OBJECT_0 + * ev == WAIT_OBJECT_0 + 1 + * ev == WAIT_ABANDONED_0 + 1 + */ + + if (ngx_terminate || ngx_quit || ngx_exiting) { + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting"); + return 0; + } + + break; + } + + for ( ;; ) { + + if (ngx_terminate || ngx_quit || ngx_exiting) { + ngx_log_error(NGX_LOG_NOTICE, cycle->log, 0, "exiting"); + break; + } + + ngx_cache_manager_process_handler(); + } + + if (ReleaseMutex(ngx_cache_manager_mutex) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "ReleaseMutex() failed"); + } + + return 0; +} + + +static void +ngx_cache_manager_process_handler(void) +{ + u_long ev; + ngx_uint_t i; + ngx_msec_t next, n; + ngx_path_t **path; + + next = 60 * 60 * 1000; + + path = ngx_cycle->paths.elts; + for (i = 0; i < ngx_cycle->paths.nelts; i++) { + + if (path[i]->manager) { + n = path[i]->manager(path[i]->data); + + next = (n <= next) ? n : next; + + ngx_time_update(); + } + } + + if (next == 0) { + next = 1; + } + + ev = WaitForSingleObject(ngx_cache_manager_event, (u_long) next); + + if (ev != WAIT_TIMEOUT) { + + ngx_time_update(); + + ngx_log_debug1(NGX_LOG_DEBUG_CORE, ngx_cycle->log, 0, + "cache manager WaitForSingleObject: %ul", ev); + } +} + + +static ngx_thread_value_t __stdcall +ngx_cache_loader_thread(void *data) +{ + ngx_uint_t i; + ngx_path_t **path; + ngx_cycle_t *cycle; + + ngx_msleep(60000); + + cycle = (ngx_cycle_t *) ngx_cycle; + + path = cycle->paths.elts; + for (i = 0; i < cycle->paths.nelts; i++) { + + if (ngx_terminate || ngx_quit || ngx_exiting) { + break; + } + + if (path[i]->loader) { + path[i]->loader(path[i]->data); + ngx_time_update(); + } + } + + return 0; +} + + +void +ngx_single_process_cycle(ngx_cycle_t *cycle) +{ + ngx_tid_t tid; + + ngx_console_init(cycle); + + if (ngx_create_signal_events(cycle) != NGX_OK) { + exit(2); + } + + if (ngx_create_thread(&tid, ngx_worker_thread, NULL, cycle->log) != 0) { + /* fatal */ + exit(2); + } + + /* STUB */ + WaitForSingleObject(ngx_stop_event, INFINITE); +} + + +ngx_int_t +ngx_os_signal_process(ngx_cycle_t *cycle, char *sig, ngx_pid_t pid) +{ + HANDLE ev; + ngx_int_t rc; + char evn[NGX_PROCESS_SYNC_NAME]; + + ngx_sprintf((u_char *) evn, "Global\\ngx_%s_%P%Z", sig, pid); + + ev = OpenEvent(EVENT_MODIFY_STATE, 0, evn); + if (ev == NULL) { + ngx_log_error(NGX_LOG_ERR, cycle->log, ngx_errno, + "OpenEvent(\"%s\") failed", evn); + return 1; + } + + if (SetEvent(ev) == 0) { + ngx_log_error(NGX_LOG_ALERT, cycle->log, ngx_errno, + "SetEvent(\"%s\") failed", evn); + rc = 1; + + } else { + rc = 0; + } + + ngx_close_handle(ev); + + return rc; +} + + +void +ngx_close_handle(HANDLE h) +{ + if (CloseHandle(h) == 0) { + ngx_log_error(NGX_LOG_ALERT, ngx_cycle->log, ngx_errno, + "CloseHandle(%p) failed", h); + } +} diff --git a/src/os/win32/ngx_process_cycle.h b/src/os/win32/ngx_process_cycle.h new file mode 100644 index 0000000..95d2743 --- /dev/null +++ b/src/os/win32/ngx_process_cycle.h @@ -0,0 +1,44 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_PROCESS_CYCLE_H_INCLUDED_ +#define _NGX_PROCESS_CYCLE_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +#define NGX_PROCESS_SINGLE 0 +#define NGX_PROCESS_MASTER 1 +#define NGX_PROCESS_SIGNALLER 2 +#define NGX_PROCESS_WORKER 3 + + +void ngx_master_process_cycle(ngx_cycle_t *cycle); +void ngx_single_process_cycle(ngx_cycle_t *cycle); +void ngx_close_handle(HANDLE h); + + +extern ngx_uint_t ngx_process; +extern ngx_uint_t ngx_worker; +extern ngx_pid_t ngx_pid; +extern ngx_uint_t ngx_exiting; + +extern sig_atomic_t ngx_quit; +extern sig_atomic_t ngx_terminate; +extern sig_atomic_t ngx_reopen; + +extern ngx_uint_t ngx_inherited; +extern ngx_pid_t ngx_new_binary; + + +extern HANDLE ngx_master_process_event; +extern char ngx_master_process_event_name[]; + + +#endif /* _NGX_PROCESS_CYCLE_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_service.c b/src/os/win32/ngx_service.c new file mode 100644 index 0000000..835d9cf --- /dev/null +++ b/src/os/win32/ngx_service.c @@ -0,0 +1,134 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + + +#define NGX_SERVICE_CONTROL_SHUTDOWN 128 +#define NGX_SERVICE_CONTROL_REOPEN 129 + + +SERVICE_TABLE_ENTRY st[] = { + { "nginx", service_main }, + { NULL, NULL } +}; + + +ngx_int_t +ngx_service(ngx_log_t *log) +{ + /* primary thread */ + + /* StartServiceCtrlDispatcher() should be called within 30 seconds */ + + if (StartServiceCtrlDispatcher(st) == 0) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "StartServiceCtrlDispatcher() failed"); + return NGX_ERROR; + } + + return NGX_OK; +} + + +void +service_main(u_int argc, char **argv) +{ + SERVICE_STATUS status; + SERVICE_STATUS_HANDLE service; + + /* thread spawned by SCM */ + + service = RegisterServiceCtrlHandlerEx("nginx", service_handler, ctx); + if (service == INVALID_HANDLE_VALUE) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "RegisterServiceCtrlHandlerEx() failed"); + return; + } + + status.dwServiceType = SERVICE_WIN32_OWN_PROCESS; + status.dwCurrentState = SERVICE_START_PENDING; + status.dwControlsAccepted = SERVICE_ACCEPT_STOP + |SERVICE_ACCEPT_PARAMCHANGE; + status.dwWin32ExitCode = NO_ERROR; + status.dwServiceSpecificExitCode = 0; + status.dwCheckPoint = 1; + status.dwWaitHint = 2000; + + /* SetServiceStatus() should be called within 80 seconds */ + + if (SetServiceStatus(service, &status) == 0) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "SetServiceStatus() failed"); + return; + } + + /* init */ + + status.dwCurrentState = SERVICE_RUNNING; + status.dwCheckPoint = 0; + status.dwWaitHint = 0; + + if (SetServiceStatus(service, &status) == 0) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "SetServiceStatus() failed"); + return; + } + + /* call master or worker loop */ + + /* + * master should use event notification and look status + * single should use iocp to get notifications from service handler + */ + +} + + +u_int +service_handler(u_int control, u_int type, void *data, void *ctx) +{ + /* primary thread */ + + switch (control) { + + case SERVICE_CONTROL_INTERROGATE: + status = NGX_IOCP_INTERROGATE; + break; + + case SERVICE_CONTROL_STOP: + status = NGX_IOCP_STOP; + break; + + case SERVICE_CONTROL_PARAMCHANGE: + status = NGX_IOCP_RECONFIGURE; + break; + + case NGX_SERVICE_CONTROL_SHUTDOWN: + status = NGX_IOCP_REOPEN; + break; + + case NGX_SERVICE_CONTROL_REOPEN: + status = NGX_IOCP_REOPEN; + break; + + default: + return ERROR_CALL_NOT_IMPLEMENTED; + } + + if (ngx_single) { + if (PostQueuedCompletionStatus(iocp, ... status, ...) == 0) { + err = ngx_errno; + ngx_log_error(NGX_LOG_ALERT, log, err, + "PostQueuedCompletionStatus() failed"); + return err; + } + + } else { + Event + } + + return NO_ERROR; +} diff --git a/src/os/win32/ngx_shmem.c b/src/os/win32/ngx_shmem.c new file mode 100644 index 0000000..c3ed699 --- /dev/null +++ b/src/os/win32/ngx_shmem.c @@ -0,0 +1,161 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +/* + * Base addresses selected by system for shared memory mappings are likely + * to be different on Windows Vista and later versions due to address space + * layout randomization. This is however incompatible with storing absolute + * addresses within the shared memory. + * + * To make it possible to store absolute addresses we create mappings + * at the same address in all processes by starting mappings at predefined + * addresses. The addresses were selected somewhat randomly in order to + * minimize the probability that some other library doing something similar + * conflicts with us. The addresses are from the following typically free + * blocks: + * + * - 0x10000000 .. 0x70000000 (about 1.5 GB in total) on 32-bit platforms + * - 0x000000007fff0000 .. 0x000007f68e8b0000 (about 8 TB) on 64-bit platforms + * + * Additionally, we allow to change the mapping address once it was detected + * to be different from one originally used. This is needed to support + * reconfiguration. + */ + + +#ifdef _WIN64 +#define NGX_SHMEM_BASE 0x0000047047e00000 +#else +#define NGX_SHMEM_BASE 0x2efe0000 +#endif + + +ngx_uint_t ngx_allocation_granularity; + + +ngx_int_t +ngx_shm_alloc(ngx_shm_t *shm) +{ + u_char *name; + uint64_t size; + static u_char *base = (u_char *) NGX_SHMEM_BASE; + + name = ngx_alloc(shm->name.len + 2 + NGX_INT32_LEN, shm->log); + if (name == NULL) { + return NGX_ERROR; + } + + (void) ngx_sprintf(name, "%V_%s%Z", &shm->name, ngx_unique); + + ngx_set_errno(0); + + size = shm->size; + + shm->handle = CreateFileMapping(INVALID_HANDLE_VALUE, NULL, PAGE_READWRITE, + (u_long) (size >> 32), + (u_long) (size & 0xffffffff), + (char *) name); + + if (shm->handle == NULL) { + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "CreateFileMapping(%uz, %s) failed", + shm->size, name); + ngx_free(name); + + return NGX_ERROR; + } + + ngx_free(name); + + if (ngx_errno == ERROR_ALREADY_EXISTS) { + shm->exists = 1; + } + + shm->addr = MapViewOfFileEx(shm->handle, FILE_MAP_WRITE, 0, 0, 0, base); + + if (shm->addr != NULL) { + base += ngx_align(size, ngx_allocation_granularity); + return NGX_OK; + } + + ngx_log_debug3(NGX_LOG_DEBUG_CORE, shm->log, ngx_errno, + "MapViewOfFileEx(%uz, %p) of file mapping \"%V\" failed, " + "retry without a base address", + shm->size, base, &shm->name); + + /* + * Order of shared memory zones may be different in the master process + * and worker processes after reconfiguration. As a result, the above + * may fail due to a conflict with a previously created mapping remapped + * to a different address. Additionally, there may be a conflict with + * some other uses of the memory. In this case we retry without a base + * address to let the system assign the address itself. + */ + + shm->addr = MapViewOfFile(shm->handle, FILE_MAP_WRITE, 0, 0, 0); + + if (shm->addr != NULL) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "MapViewOfFile(%uz) of file mapping \"%V\" failed", + shm->size, &shm->name); + + if (CloseHandle(shm->handle) == 0) { + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "CloseHandle() of file mapping \"%V\" failed", + &shm->name); + } + + return NGX_ERROR; +} + + +ngx_int_t +ngx_shm_remap(ngx_shm_t *shm, u_char *addr) +{ + if (UnmapViewOfFile(shm->addr) == 0) { + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "UnmapViewOfFile(%p) of file mapping \"%V\" failed", + shm->addr, &shm->name); + return NGX_ERROR; + } + + shm->addr = MapViewOfFileEx(shm->handle, FILE_MAP_WRITE, 0, 0, 0, addr); + + if (shm->addr != NULL) { + return NGX_OK; + } + + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "MapViewOfFileEx(%uz, %p) of file mapping \"%V\" failed", + shm->size, addr, &shm->name); + + return NGX_ERROR; +} + + +void +ngx_shm_free(ngx_shm_t *shm) +{ + if (UnmapViewOfFile(shm->addr) == 0) { + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "UnmapViewOfFile(%p) of file mapping \"%V\" failed", + shm->addr, &shm->name); + } + + if (CloseHandle(shm->handle) == 0) { + ngx_log_error(NGX_LOG_ALERT, shm->log, ngx_errno, + "CloseHandle() of file mapping \"%V\" failed", + &shm->name); + } +} diff --git a/src/os/win32/ngx_shmem.h b/src/os/win32/ngx_shmem.h new file mode 100644 index 0000000..ee47429 --- /dev/null +++ b/src/os/win32/ngx_shmem.h @@ -0,0 +1,33 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_SHMEM_H_INCLUDED_ +#define _NGX_SHMEM_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +typedef struct { + u_char *addr; + size_t size; + ngx_str_t name; + HANDLE handle; + ngx_log_t *log; + ngx_uint_t exists; /* unsigned exists:1; */ +} ngx_shm_t; + + +ngx_int_t ngx_shm_alloc(ngx_shm_t *shm); +ngx_int_t ngx_shm_remap(ngx_shm_t *shm, u_char *addr); +void ngx_shm_free(ngx_shm_t *shm); + +extern ngx_uint_t ngx_allocation_granularity; + + +#endif /* _NGX_SHMEM_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_socket.c b/src/os/win32/ngx_socket.c new file mode 100644 index 0000000..b1b4afb --- /dev/null +++ b/src/os/win32/ngx_socket.c @@ -0,0 +1,49 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +int +ngx_nonblocking(ngx_socket_t s) +{ + unsigned long nb = 1; + + return ioctlsocket(s, FIONBIO, &nb); +} + + +int +ngx_blocking(ngx_socket_t s) +{ + unsigned long nb = 0; + + return ioctlsocket(s, FIONBIO, &nb); +} + + +int +ngx_socket_nread(ngx_socket_t s, int *n) +{ + unsigned long nread; + + if (ioctlsocket(s, FIONREAD, &nread) == -1) { + return -1; + } + + *n = nread; + + return 0; +} + + +int +ngx_tcp_push(ngx_socket_t s) +{ + return 0; +} diff --git a/src/os/win32/ngx_socket.h b/src/os/win32/ngx_socket.h new file mode 100644 index 0000000..5b61389 --- /dev/null +++ b/src/os/win32/ngx_socket.h @@ -0,0 +1,255 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_SOCKET_H_INCLUDED_ +#define _NGX_SOCKET_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +#define NGX_WRITE_SHUTDOWN SD_SEND +#define NGX_READ_SHUTDOWN SD_RECEIVE +#define NGX_RDWR_SHUTDOWN SD_BOTH + + +typedef SOCKET ngx_socket_t; +typedef int socklen_t; + + +#define ngx_socket(af, type, proto) \ + WSASocketW(af, type, proto, NULL, 0, WSA_FLAG_OVERLAPPED) + +#define ngx_socket_n "WSASocketW()" + +int ngx_nonblocking(ngx_socket_t s); +int ngx_blocking(ngx_socket_t s); + +#define ngx_nonblocking_n "ioctlsocket(FIONBIO)" +#define ngx_blocking_n "ioctlsocket(!FIONBIO)" + +int ngx_socket_nread(ngx_socket_t s, int *n); +#define ngx_socket_nread_n "ioctlsocket(FIONREAD)" + +#define ngx_shutdown_socket shutdown +#define ngx_shutdown_socket_n "shutdown()" + +#define ngx_close_socket closesocket +#define ngx_close_socket_n "closesocket()" + + +#ifndef WSAID_ACCEPTEX + +typedef BOOL (PASCAL FAR * LPFN_ACCEPTEX)( + IN SOCKET sListenSocket, + IN SOCKET sAcceptSocket, + IN PVOID lpOutputBuffer, + IN DWORD dwReceiveDataLength, + IN DWORD dwLocalAddressLength, + IN DWORD dwRemoteAddressLength, + OUT LPDWORD lpdwBytesReceived, + IN LPOVERLAPPED lpOverlapped + ); + +#define WSAID_ACCEPTEX \ + {0xb5367df1,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}} + +#endif + + +#ifndef WSAID_GETACCEPTEXSOCKADDRS + +typedef VOID (PASCAL FAR * LPFN_GETACCEPTEXSOCKADDRS)( + IN PVOID lpOutputBuffer, + IN DWORD dwReceiveDataLength, + IN DWORD dwLocalAddressLength, + IN DWORD dwRemoteAddressLength, + OUT struct sockaddr **LocalSockaddr, + OUT LPINT LocalSockaddrLength, + OUT struct sockaddr **RemoteSockaddr, + OUT LPINT RemoteSockaddrLength + ); + +#define WSAID_GETACCEPTEXSOCKADDRS \ + {0xb5367df2,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}} + +#endif + + +#ifndef WSAID_TRANSMITFILE + +#ifndef TF_DISCONNECT + +#define TF_DISCONNECT 1 +#define TF_REUSE_SOCKET 2 +#define TF_WRITE_BEHIND 4 +#define TF_USE_DEFAULT_WORKER 0 +#define TF_USE_SYSTEM_THREAD 16 +#define TF_USE_KERNEL_APC 32 + +typedef struct _TRANSMIT_FILE_BUFFERS { + LPVOID Head; + DWORD HeadLength; + LPVOID Tail; + DWORD TailLength; +} TRANSMIT_FILE_BUFFERS, *PTRANSMIT_FILE_BUFFERS, FAR *LPTRANSMIT_FILE_BUFFERS; + +#endif + +typedef BOOL (PASCAL FAR * LPFN_TRANSMITFILE)( + IN SOCKET hSocket, + IN HANDLE hFile, + IN DWORD nNumberOfBytesToWrite, + IN DWORD nNumberOfBytesPerSend, + IN LPOVERLAPPED lpOverlapped, + IN LPTRANSMIT_FILE_BUFFERS lpTransmitBuffers, + IN DWORD dwReserved + ); + +#define WSAID_TRANSMITFILE \ + {0xb5367df0,0xcbac,0x11cf,{0x95,0xca,0x00,0x80,0x5f,0x48,0xa1,0x92}} + +#endif + + +#ifndef WSAID_TRANSMITPACKETS + +/* OpenWatcom has a swapped TP_ELEMENT_FILE and TP_ELEMENT_MEMORY definition */ + +#ifndef TP_ELEMENT_FILE + +#ifdef _MSC_VER +#pragma warning(disable:4201) /* Nonstandard extension, nameless struct/union */ +#endif + +typedef struct _TRANSMIT_PACKETS_ELEMENT { + ULONG dwElFlags; +#define TP_ELEMENT_MEMORY 1 +#define TP_ELEMENT_FILE 2 +#define TP_ELEMENT_EOP 4 + ULONG cLength; + union { + struct { + LARGE_INTEGER nFileOffset; + HANDLE hFile; + }; + PVOID pBuffer; + }; +} TRANSMIT_PACKETS_ELEMENT, *PTRANSMIT_PACKETS_ELEMENT, + FAR *LPTRANSMIT_PACKETS_ELEMENT; + +#ifdef _MSC_VER +#pragma warning(default:4201) +#endif + +#endif + +typedef BOOL (PASCAL FAR * LPFN_TRANSMITPACKETS) ( + SOCKET hSocket, + TRANSMIT_PACKETS_ELEMENT *lpPacketArray, + DWORD nElementCount, + DWORD nSendSize, + LPOVERLAPPED lpOverlapped, + DWORD dwFlags + ); + +#define WSAID_TRANSMITPACKETS \ + {0xd9689da0,0x1f90,0x11d3,{0x99,0x71,0x00,0xc0,0x4f,0x68,0xc8,0x76}} + +#endif + + +#ifndef WSAID_CONNECTEX + +typedef BOOL (PASCAL FAR * LPFN_CONNECTEX) ( + IN SOCKET s, + IN const struct sockaddr FAR *name, + IN int namelen, + IN PVOID lpSendBuffer OPTIONAL, + IN DWORD dwSendDataLength, + OUT LPDWORD lpdwBytesSent, + IN LPOVERLAPPED lpOverlapped + ); + +#define WSAID_CONNECTEX \ + {0x25a207b9,0xddf3,0x4660,{0x8e,0xe9,0x76,0xe5,0x8c,0x74,0x06,0x3e}} + +#endif + + +#ifndef WSAID_DISCONNECTEX + +typedef BOOL (PASCAL FAR * LPFN_DISCONNECTEX) ( + IN SOCKET s, + IN LPOVERLAPPED lpOverlapped, + IN DWORD dwFlags, + IN DWORD dwReserved + ); + +#define WSAID_DISCONNECTEX \ + {0x7fda2e11,0x8630,0x436f,{0xa0,0x31,0xf5,0x36,0xa6,0xee,0xc1,0x57}} + +#endif + + +extern LPFN_ACCEPTEX ngx_acceptex; +extern LPFN_GETACCEPTEXSOCKADDRS ngx_getacceptexsockaddrs; +extern LPFN_TRANSMITFILE ngx_transmitfile; +extern LPFN_TRANSMITPACKETS ngx_transmitpackets; +extern LPFN_CONNECTEX ngx_connectex; +extern LPFN_DISCONNECTEX ngx_disconnectex; + + +#if (NGX_HAVE_POLL && !defined POLLIN) + +/* + * WSAPoll() is only available if _WIN32_WINNT >= 0x0600. + * If it is not available during compilation, we try to + * load it dynamically at runtime. + */ + +#define NGX_LOAD_WSAPOLL 1 + +#define POLLRDNORM 0x0100 +#define POLLRDBAND 0x0200 +#define POLLIN (POLLRDNORM | POLLRDBAND) +#define POLLPRI 0x0400 + +#define POLLWRNORM 0x0010 +#define POLLOUT (POLLWRNORM) +#define POLLWRBAND 0x0020 + +#define POLLERR 0x0001 +#define POLLHUP 0x0002 +#define POLLNVAL 0x0004 + +typedef struct pollfd { + + SOCKET fd; + SHORT events; + SHORT revents; + +} WSAPOLLFD, *PWSAPOLLFD, FAR *LPWSAPOLLFD; + +typedef int (WSAAPI *ngx_wsapoll_pt)( + LPWSAPOLLFD fdArray, + ULONG fds, + INT timeout + ); + +extern ngx_wsapoll_pt WSAPoll; +extern ngx_uint_t ngx_have_wsapoll; + +#endif + + +int ngx_tcp_push(ngx_socket_t s); +#define ngx_tcp_push_n "tcp_push()" + + +#endif /* _NGX_SOCKET_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_stat.c b/src/os/win32/ngx_stat.c new file mode 100644 index 0000000..51bcd96 --- /dev/null +++ b/src/os/win32/ngx_stat.c @@ -0,0 +1,34 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +int ngx_file_type(char *file, ngx_file_info_t *sb) +{ + sb->dwFileAttributes = GetFileAttributes(file); + + if (sb->dwFileAttributes == INVALID_FILE_ATTRIBUTES) { + return -1; + } + + return 0; +} + +/* +int ngx_stat(char *file, ngx_stat_t *sb) +{ + *sb = GetFileAttributes(file); + + if (*sb == INVALID_FILE_ATTRIBUTES) { + return -1; + } + + return 0; +} +*/ diff --git a/src/os/win32/ngx_thread.c b/src/os/win32/ngx_thread.c new file mode 100644 index 0000000..a13de2d --- /dev/null +++ b/src/os/win32/ngx_thread.c @@ -0,0 +1,30 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +ngx_err_t +ngx_create_thread(ngx_tid_t *tid, + ngx_thread_value_t (__stdcall *func)(void *arg), void *arg, ngx_log_t *log) +{ + u_long id; + ngx_err_t err; + + *tid = CreateThread(NULL, 0, func, arg, 0, &id); + + if (*tid != NULL) { + ngx_log_error(NGX_LOG_NOTICE, log, 0, + "create thread " NGX_TID_T_FMT, id); + return 0; + } + + err = ngx_errno; + ngx_log_error(NGX_LOG_ALERT, log, err, "CreateThread() failed"); + return err; +} diff --git a/src/os/win32/ngx_thread.h b/src/os/win32/ngx_thread.h new file mode 100644 index 0000000..4012276 --- /dev/null +++ b/src/os/win32/ngx_thread.h @@ -0,0 +1,27 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_THREAD_H_INCLUDED_ +#define _NGX_THREAD_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +typedef HANDLE ngx_tid_t; +typedef DWORD ngx_thread_value_t; + + +ngx_err_t ngx_create_thread(ngx_tid_t *tid, + ngx_thread_value_t (__stdcall *func)(void *arg), void *arg, ngx_log_t *log); + +#define ngx_log_tid GetCurrentThreadId() +#define NGX_TID_T_FMT "%ud" + + +#endif /* _NGX_THREAD_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_time.c b/src/os/win32/ngx_time.c new file mode 100644 index 0000000..79149b2 --- /dev/null +++ b/src/os/win32/ngx_time.c @@ -0,0 +1,83 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +void +ngx_gettimeofday(struct timeval *tp) +{ + uint64_t intervals; + FILETIME ft; + + GetSystemTimeAsFileTime(&ft); + + /* + * A file time is a 64-bit value that represents the number + * of 100-nanosecond intervals that have elapsed since + * January 1, 1601 12:00 A.M. UTC. + * + * Between January 1, 1970 (Epoch) and January 1, 1601 there were + * 134774 days, + * 11644473600 seconds or + * 11644473600,000,000,0 100-nanosecond intervals. + * + * See also MSKB Q167296. + */ + + intervals = ((uint64_t) ft.dwHighDateTime << 32) | ft.dwLowDateTime; + intervals -= 116444736000000000; + + tp->tv_sec = (long) (intervals / 10000000); + tp->tv_usec = (long) ((intervals % 10000000) / 10); +} + + +void +ngx_libc_localtime(time_t s, struct tm *tm) +{ + struct tm *t; + + t = localtime(&s); + *tm = *t; +} + + +void +ngx_libc_gmtime(time_t s, struct tm *tm) +{ + struct tm *t; + + t = gmtime(&s); + *tm = *t; +} + + +ngx_int_t +ngx_gettimezone(void) +{ + u_long n; + TIME_ZONE_INFORMATION tz; + + n = GetTimeZoneInformation(&tz); + + switch (n) { + + case TIME_ZONE_ID_UNKNOWN: + return -tz.Bias; + + case TIME_ZONE_ID_STANDARD: + return -(tz.Bias + tz.StandardBias); + + case TIME_ZONE_ID_DAYLIGHT: + return -(tz.Bias + tz.DaylightBias); + + default: /* TIME_ZONE_ID_INVALID */ + return 0; + } +} diff --git a/src/os/win32/ngx_time.h b/src/os/win32/ngx_time.h new file mode 100644 index 0000000..6c2f806 --- /dev/null +++ b/src/os/win32/ngx_time.h @@ -0,0 +1,51 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_TIME_H_INCLUDED_ +#define _NGX_TIME_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +typedef ngx_rbtree_key_t ngx_msec_t; +typedef ngx_rbtree_key_int_t ngx_msec_int_t; + +typedef SYSTEMTIME ngx_tm_t; +typedef FILETIME ngx_mtime_t; + +#define ngx_tm_sec wSecond +#define ngx_tm_min wMinute +#define ngx_tm_hour wHour +#define ngx_tm_mday wDay +#define ngx_tm_mon wMonth +#define ngx_tm_year wYear +#define ngx_tm_wday wDayOfWeek + +#define ngx_tm_sec_t u_short +#define ngx_tm_min_t u_short +#define ngx_tm_hour_t u_short +#define ngx_tm_mday_t u_short +#define ngx_tm_mon_t u_short +#define ngx_tm_year_t u_short +#define ngx_tm_wday_t u_short + + +#define ngx_msleep Sleep + +#define NGX_HAVE_GETTIMEZONE 1 + +#define ngx_timezone_update() + +ngx_int_t ngx_gettimezone(void); +void ngx_libc_localtime(time_t s, struct tm *tm); +void ngx_libc_gmtime(time_t s, struct tm *tm); +void ngx_gettimeofday(struct timeval *tp); + + +#endif /* _NGX_TIME_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_udp_wsarecv.c b/src/os/win32/ngx_udp_wsarecv.c new file mode 100644 index 0000000..5424375 --- /dev/null +++ b/src/os/win32/ngx_udp_wsarecv.c @@ -0,0 +1,149 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> + + +ssize_t +ngx_udp_wsarecv(ngx_connection_t *c, u_char *buf, size_t size) +{ + int rc; + u_long bytes, flags; + WSABUF wsabuf[1]; + ngx_err_t err; + ngx_event_t *rev; + + wsabuf[0].buf = (char *) buf; + wsabuf[0].len = size; + flags = 0; + bytes = 0; + + rc = WSARecv(c->fd, wsabuf, 1, &bytes, &flags, NULL, NULL); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: fd:%d rc:%d %ul of %z", c->fd, rc, bytes, size); + + rev = c->read; + + if (rc == -1) { + rev->ready = 0; + err = ngx_socket_errno; + + if (err == WSAEWOULDBLOCK) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSARecv() not ready"); + return NGX_AGAIN; + } + + rev->error = 1; + ngx_connection_error(c, err, "WSARecv() failed"); + + return NGX_ERROR; + } + + return bytes; +} + + +ssize_t +ngx_udp_overlapped_wsarecv(ngx_connection_t *c, u_char *buf, size_t size) +{ + int rc; + u_long bytes, flags; + WSABUF wsabuf[1]; + ngx_err_t err; + ngx_event_t *rev; + LPWSAOVERLAPPED ovlp; + + rev = c->read; + + if (!rev->ready) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "second wsa post"); + return NGX_AGAIN; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "rev->complete: %d", rev->complete); + + if (rev->complete) { + rev->complete = 0; + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + if (rev->ovlp.error) { + ngx_connection_error(c, rev->ovlp.error, "WSARecv() failed"); + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv ovlp: fd:%d %ul of %z", + c->fd, rev->available, size); + + return rev->available; + } + + if (WSAGetOverlappedResult(c->fd, (LPWSAOVERLAPPED) &rev->ovlp, + &bytes, 0, NULL) + == 0) + { + ngx_connection_error(c, ngx_socket_errno, + "WSARecv() or WSAGetOverlappedResult() failed"); + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: fd:%d %ul of %z", c->fd, bytes, size); + + return bytes; + } + + ovlp = (LPWSAOVERLAPPED) &rev->ovlp; + ngx_memzero(ovlp, sizeof(WSAOVERLAPPED)); + wsabuf[0].buf = (char *) buf; + wsabuf[0].len = size; + flags = 0; + bytes = 0; + + rc = WSARecv(c->fd, wsabuf, 1, &bytes, &flags, ovlp, NULL); + + rev->complete = 0; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv ovlp: fd:%d rc:%d %ul of %z", + c->fd, rc, bytes, size); + + if (rc == -1) { + err = ngx_socket_errno; + if (err == WSA_IO_PENDING) { + rev->active = 1; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSARecv() posted"); + return NGX_AGAIN; + } + + rev->error = 1; + ngx_connection_error(c, err, "WSARecv() failed"); + return NGX_ERROR; + } + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + + /* + * if a socket was bound with I/O completion port + * then GetQueuedCompletionStatus() would anyway return its status + * despite that WSARecv() was already complete + */ + + rev->active = 1; + return NGX_AGAIN; + } + + rev->active = 0; + + return bytes; +} diff --git a/src/os/win32/ngx_user.c b/src/os/win32/ngx_user.c new file mode 100644 index 0000000..ea6da5a --- /dev/null +++ b/src/os/win32/ngx_user.c @@ -0,0 +1,23 @@ +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +#if (NGX_CRYPT) + +ngx_int_t +ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted) +{ + /* STUB: a plain text password */ + + *encrypted = key; + + return NGX_OK; +} + +#endif /* NGX_CRYPT */ diff --git a/src/os/win32/ngx_user.h b/src/os/win32/ngx_user.h new file mode 100644 index 0000000..61408e4 --- /dev/null +++ b/src/os/win32/ngx_user.h @@ -0,0 +1,25 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_USER_H_INCLUDED_ +#define _NGX_USER_H_INCLUDED_ + + +#include <ngx_config.h> +#include <ngx_core.h> + + +/* STUB */ +#define ngx_uid_t ngx_int_t +#define ngx_gid_t ngx_int_t + + +ngx_int_t ngx_libc_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, + u_char **encrypted); + + +#endif /* _NGX_USER_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_win32_config.h b/src/os/win32/ngx_win32_config.h new file mode 100644 index 0000000..7045613 --- /dev/null +++ b/src/os/win32/ngx_win32_config.h @@ -0,0 +1,291 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#ifndef _NGX_WIN32_CONFIG_H_INCLUDED_ +#define _NGX_WIN32_CONFIG_H_INCLUDED_ + + +#undef WIN32 +#define WIN32 0x0400 +#define _WIN32_WINNT 0x0501 + + +#define STRICT +#define WIN32_LEAN_AND_MEAN + +/* enable getenv() and gmtime() in msvc8 */ +#define _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_DEPRECATE + +/* enable gethostbyname() in msvc2015 */ +#if !(NGX_HAVE_INET6) +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif + +/* + * we need to include <windows.h> explicitly before <winsock2.h> because + * the warning 4201 is enabled in <windows.h> + */ +#include <windows.h> + +#ifdef _MSC_VER +#pragma warning(disable:4201) +#endif + +#include <winsock2.h> +#include <ws2tcpip.h> /* ipv6 */ +#include <mswsock.h> +#include <shellapi.h> +#include <stddef.h> /* offsetof() */ + +#ifdef __MINGW64_VERSION_MAJOR + +/* GCC MinGW-w64 supports _FILE_OFFSET_BITS */ +#define _FILE_OFFSET_BITS 64 + +#elif defined __GNUC__ + +/* GCC MinGW's stdio.h includes sys/types.h */ +#define _OFF_T_ +#define __have_typedef_off_t + +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#ifdef __GNUC__ +#include <stdint.h> +#endif +#include <ctype.h> +#include <locale.h> + +#ifdef __WATCOMC__ +#define _TIME_T_DEFINED +typedef long time_t; +/* OpenWatcom defines time_t as "unsigned long" */ +#endif + +#include <time.h> /* localtime(), strftime() */ + + +#ifdef _MSC_VER + +/* the end of the precompiled headers */ +#pragma hdrstop + +#pragma warning(default:4201) + +/* 'type cast': from function pointer to data pointer */ +#pragma warning(disable:4054) + +/* 'type cast': from data pointer to function pointer */ +#pragma warning(disable:4055) + +/* 'function' : different 'const' qualifiers */ +#pragma warning(disable:4090) + +/* unreferenced formal parameter */ +#pragma warning(disable:4100) + +/* FD_SET() and FD_CLR(): conditional expression is constant */ +#pragma warning(disable:4127) + +/* conversion from 'type1' to 'type2', possible loss of data */ +#pragma warning(disable:4244) + +/* conversion from 'size_t' to 'type', possible loss of data */ +#pragma warning(disable:4267) + +/* array is too small to include a terminating null character */ +#pragma warning(disable:4295) + +/* conversion from 'type1' to 'type2' of greater size */ +#pragma warning(disable:4306) + +#endif + + +#ifdef __WATCOMC__ + +/* symbol 'ngx_rbtree_min' has been defined, but not referenced */ +#pragma disable_message(202) + +#endif + + +#ifdef __BORLANDC__ + +/* the end of the precompiled headers */ +#pragma hdrstop + +/* functions containing (for|while|some if) are not expanded inline */ +#pragma warn -8027 + +/* unreferenced formal parameter */ +#pragma warn -8057 + +/* suspicious pointer arithmetic */ +#pragma warn -8072 + +#endif + + +#include <ngx_auto_config.h> + + +#define ngx_inline __inline +#define ngx_cdecl __cdecl + + +#ifdef _MSC_VER +typedef unsigned __int32 uint32_t; +typedef __int32 int32_t; +typedef unsigned __int16 uint16_t; +#define ngx_libc_cdecl __cdecl + +#elif defined __BORLANDC__ +typedef unsigned __int32 uint32_t; +typedef __int32 int32_t; +typedef unsigned __int16 uint16_t; +#define ngx_libc_cdecl __cdecl + +#else /* __WATCOMC__ */ +typedef unsigned int uint32_t; +typedef int int32_t; +typedef unsigned short int uint16_t; +#define ngx_libc_cdecl + +#endif + +typedef __int64 int64_t; +typedef unsigned __int64 uint64_t; + +#if __BORLANDC__ +typedef int intptr_t; +typedef u_int uintptr_t; +#endif + + +#ifndef __MINGW64_VERSION_MAJOR + +/* Windows defines off_t as long, which is 32-bit */ +typedef __int64 off_t; +#define _OFF_T_DEFINED + +#endif + + +#ifdef __WATCOMC__ + +/* off_t is redefined by sys/types.h used by zlib.h */ +#define __TYPES_H_INCLUDED +typedef int dev_t; +typedef unsigned int ino_t; + +#elif __BORLANDC__ + +/* off_t is redefined by sys/types.h used by zlib.h */ +#define __TYPES_H + +typedef int dev_t; +typedef unsigned int ino_t; + +#endif + + +#ifndef __GNUC__ +#ifdef _WIN64 +typedef __int64 ssize_t; +#else +typedef int ssize_t; +#endif +#endif + + +typedef uint32_t in_addr_t; +typedef u_short in_port_t; +typedef int sig_atomic_t; + + +#ifdef _WIN64 + +#define NGX_PTR_SIZE 8 +#define NGX_SIZE_T_LEN (sizeof("-9223372036854775808") - 1) +#define NGX_MAX_SIZE_T_VALUE 9223372036854775807 +#define NGX_TIME_T_LEN (sizeof("-9223372036854775808") - 1) +#define NGX_TIME_T_SIZE 8 +#define NGX_MAX_TIME_T_VALUE 9223372036854775807 + +#else + +#define NGX_PTR_SIZE 4 +#define NGX_SIZE_T_LEN (sizeof("-2147483648") - 1) +#define NGX_MAX_SIZE_T_VALUE 2147483647 +#define NGX_TIME_T_LEN (sizeof("-2147483648") - 1) +#define NGX_TIME_T_SIZE 4 +#define NGX_MAX_TIME_T_VALUE 2147483647 + +#endif + + +#define NGX_OFF_T_LEN (sizeof("-9223372036854775807") - 1) +#define NGX_MAX_OFF_T_VALUE 9223372036854775807 +#define NGX_SIG_ATOMIC_T_SIZE 4 + + +#define NGX_HAVE_LITTLE_ENDIAN 1 +#define NGX_HAVE_NONALIGNED 1 + + +#define NGX_WIN_NT 200000 + + +#define NGX_LISTEN_BACKLOG 511 + + +#ifndef NGX_HAVE_INHERITED_NONBLOCK +#define NGX_HAVE_INHERITED_NONBLOCK 1 +#endif + +#ifndef NGX_HAVE_CASELESS_FILESYSTEM +#define NGX_HAVE_CASELESS_FILESYSTEM 1 +#endif + +#ifndef NGX_HAVE_WIN32_TRANSMITPACKETS +#define NGX_HAVE_WIN32_TRANSMITPACKETS 1 +#define NGX_HAVE_WIN32_TRANSMITFILE 0 +#endif + +#ifndef NGX_HAVE_WIN32_TRANSMITFILE +#define NGX_HAVE_WIN32_TRANSMITFILE 1 +#endif + +#if (NGX_HAVE_WIN32_TRANSMITPACKETS) || (NGX_HAVE_WIN32_TRANSMITFILE) +#define NGX_HAVE_SENDFILE 1 +#endif + +#ifndef NGX_HAVE_SO_SNDLOWAT +/* setsockopt(SO_SNDLOWAT) returns error WSAENOPROTOOPT */ +#define NGX_HAVE_SO_SNDLOWAT 0 +#endif + +#ifndef NGX_HAVE_FIONREAD +#define NGX_HAVE_FIONREAD 1 +#endif + +#define NGX_HAVE_GETADDRINFO 1 + +#define ngx_random() \ + ((long) (0x7fffffff & ( ((uint32_t) rand() << 16) \ + ^ ((uint32_t) rand() << 8) \ + ^ ((uint32_t) rand()) ))) + +#define ngx_debug_init() + + +#endif /* _NGX_WIN32_CONFIG_H_INCLUDED_ */ diff --git a/src/os/win32/ngx_win32_init.c b/src/os/win32/ngx_win32_init.c new file mode 100644 index 0000000..de66a44 --- /dev/null +++ b/src/os/win32/ngx_win32_init.c @@ -0,0 +1,329 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <nginx.h> + + +ngx_uint_t ngx_win32_version; +ngx_uint_t ngx_ncpu; +ngx_uint_t ngx_max_wsabufs; +ngx_int_t ngx_max_sockets; +ngx_uint_t ngx_inherited_nonblocking = 1; +ngx_uint_t ngx_tcp_nodelay_and_tcp_nopush; + +char ngx_unique[NGX_INT32_LEN + 1]; + + +ngx_os_io_t ngx_os_io = { + ngx_wsarecv, + ngx_wsarecv_chain, + ngx_udp_wsarecv, + ngx_wsasend, + NULL, + NULL, + ngx_wsasend_chain, + 0 +}; + + +typedef struct { + WORD wServicePackMinor; + WORD wSuiteMask; + BYTE wProductType; +} ngx_osviex_stub_t; + + +static u_int osviex; +static OSVERSIONINFOEX osvi; + +/* Should these pointers be per protocol ? */ +LPFN_ACCEPTEX ngx_acceptex; +LPFN_GETACCEPTEXSOCKADDRS ngx_getacceptexsockaddrs; +LPFN_TRANSMITFILE ngx_transmitfile; +LPFN_TRANSMITPACKETS ngx_transmitpackets; +LPFN_CONNECTEX ngx_connectex; +LPFN_DISCONNECTEX ngx_disconnectex; + +static GUID ax_guid = WSAID_ACCEPTEX; +static GUID as_guid = WSAID_GETACCEPTEXSOCKADDRS; +static GUID tf_guid = WSAID_TRANSMITFILE; +static GUID tp_guid = WSAID_TRANSMITPACKETS; +static GUID cx_guid = WSAID_CONNECTEX; +static GUID dx_guid = WSAID_DISCONNECTEX; + + +#if (NGX_LOAD_WSAPOLL) +ngx_wsapoll_pt WSAPoll; +ngx_uint_t ngx_have_wsapoll; +#endif + + +ngx_int_t +ngx_os_init(ngx_log_t *log) +{ + DWORD bytes; + SOCKET s; + WSADATA wsd; + ngx_err_t err; + ngx_time_t *tp; + ngx_uint_t n; + SYSTEM_INFO si; + + /* get Windows version */ + + ngx_memzero(&osvi, sizeof(OSVERSIONINFOEX)); + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + +#ifdef _MSC_VER +#pragma warning(disable:4996) +#endif + + osviex = GetVersionEx((OSVERSIONINFO *) &osvi); + + if (osviex == 0) { + osvi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOEX); + if (GetVersionEx((OSVERSIONINFO *) &osvi) == 0) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_errno, + "GetVersionEx() failed"); + return NGX_ERROR; + } + } + +#ifdef _MSC_VER +#pragma warning(default:4996) +#endif + + /* + * Windows 3.1 Win32s 0xxxxx + * + * Windows 95 140000 + * Windows 98 141000 + * Windows ME 149000 + * Windows NT 3.51 235100 + * Windows NT 4.0 240000 + * Windows NT 4.0 SP5 240050 + * Windows 2000 250000 + * Windows XP 250100 + * Windows 2003 250200 + * Windows Vista/2008 260000 + * + * Windows CE x.x 3xxxxx + */ + + ngx_win32_version = osvi.dwPlatformId * 100000 + + osvi.dwMajorVersion * 10000 + + osvi.dwMinorVersion * 100; + + if (osviex) { + ngx_win32_version += osvi.wServicePackMajor * 10 + + osvi.wServicePackMinor; + } + + GetSystemInfo(&si); + ngx_pagesize = si.dwPageSize; + ngx_allocation_granularity = si.dwAllocationGranularity; + ngx_ncpu = si.dwNumberOfProcessors; + ngx_cacheline_size = NGX_CPU_CACHE_LINE; + + for (n = ngx_pagesize; n >>= 1; ngx_pagesize_shift++) { /* void */ } + + /* delete default "C" locale for _wcsicmp() */ + setlocale(LC_ALL, ""); + + + /* init Winsock */ + + if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + "WSAStartup() failed"); + return NGX_ERROR; + } + + if (ngx_win32_version < NGX_WIN_NT) { + ngx_max_wsabufs = 16; + return NGX_OK; + } + + /* STUB: ngx_uint_t max */ + ngx_max_wsabufs = 1024 * 1024; + + /* + * get AcceptEx(), GetAcceptExSockAddrs(), TransmitFile(), + * TransmitPackets(), ConnectEx(), and DisconnectEx() addresses + */ + + s = ngx_socket(AF_INET, SOCK_STREAM, IPPROTO_IP); + if (s == (ngx_socket_t) -1) { + ngx_log_error(NGX_LOG_EMERG, log, ngx_socket_errno, + ngx_socket_n " failed"); + return NGX_ERROR; + } + + if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &ax_guid, sizeof(GUID), + &ngx_acceptex, sizeof(LPFN_ACCEPTEX), &bytes, NULL, NULL) + == -1) + { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_socket_errno, + "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, " + "WSAID_ACCEPTEX) failed"); + } + + if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &as_guid, sizeof(GUID), + &ngx_getacceptexsockaddrs, sizeof(LPFN_GETACCEPTEXSOCKADDRS), + &bytes, NULL, NULL) + == -1) + { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_socket_errno, + "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, " + "WSAID_GETACCEPTEXSOCKADDRS) failed"); + } + + if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &tf_guid, sizeof(GUID), + &ngx_transmitfile, sizeof(LPFN_TRANSMITFILE), &bytes, + NULL, NULL) + == -1) + { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_socket_errno, + "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, " + "WSAID_TRANSMITFILE) failed"); + } + + if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &tp_guid, sizeof(GUID), + &ngx_transmitpackets, sizeof(LPFN_TRANSMITPACKETS), &bytes, + NULL, NULL) + == -1) + { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_socket_errno, + "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, " + "WSAID_TRANSMITPACKETS) failed"); + } + + if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &cx_guid, sizeof(GUID), + &ngx_connectex, sizeof(LPFN_CONNECTEX), &bytes, + NULL, NULL) + == -1) + { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_socket_errno, + "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, " + "WSAID_CONNECTEX) failed"); + } + + if (WSAIoctl(s, SIO_GET_EXTENSION_FUNCTION_POINTER, &dx_guid, sizeof(GUID), + &ngx_disconnectex, sizeof(LPFN_DISCONNECTEX), &bytes, + NULL, NULL) + == -1) + { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_socket_errno, + "WSAIoctl(SIO_GET_EXTENSION_FUNCTION_POINTER, " + "WSAID_DISCONNECTEX) failed"); + } + + if (ngx_close_socket(s) == -1) { + ngx_log_error(NGX_LOG_ALERT, log, ngx_socket_errno, + ngx_close_socket_n " failed"); + } + +#if (NGX_LOAD_WSAPOLL) + { + HMODULE hmod; + + hmod = GetModuleHandle("ws2_32.dll"); + if (hmod == NULL) { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_errno, + "GetModuleHandle(\"ws2_32.dll\") failed"); + goto nopoll; + } + + WSAPoll = (ngx_wsapoll_pt) (void *) GetProcAddress(hmod, "WSAPoll"); + if (WSAPoll == NULL) { + ngx_log_error(NGX_LOG_NOTICE, log, ngx_errno, + "GetProcAddress(\"WSAPoll\") failed"); + goto nopoll; + } + + ngx_have_wsapoll = 1; + + } + +nopoll: + +#endif + + if (GetEnvironmentVariable("ngx_unique", ngx_unique, NGX_INT32_LEN + 1) + != 0) + { + ngx_process = NGX_PROCESS_WORKER; + + } else { + err = ngx_errno; + + if (err != ERROR_ENVVAR_NOT_FOUND) { + ngx_log_error(NGX_LOG_EMERG, log, err, + "GetEnvironmentVariable(\"ngx_unique\") failed"); + return NGX_ERROR; + } + + ngx_sprintf((u_char *) ngx_unique, "%P%Z", ngx_pid); + } + + tp = ngx_timeofday(); + srand((ngx_pid << 16) ^ (unsigned) tp->sec ^ tp->msec); + + return NGX_OK; +} + + +void +ngx_os_status(ngx_log_t *log) +{ + ngx_osviex_stub_t *osviex_stub; + + ngx_log_error(NGX_LOG_NOTICE, log, 0, NGINX_VER_BUILD); + + if (osviex) { + + /* + * the MSVC 6.0 SP2 defines wSuiteMask and wProductType + * as WORD wReserved[2] + */ + osviex_stub = (ngx_osviex_stub_t *) &osvi.wServicePackMinor; + + ngx_log_error(NGX_LOG_INFO, log, 0, + "OS: %ui build:%ud, \"%s\", suite:%Xd, type:%ud", + ngx_win32_version, osvi.dwBuildNumber, osvi.szCSDVersion, + osviex_stub->wSuiteMask, osviex_stub->wProductType); + + } else { + if (osvi.dwPlatformId == VER_PLATFORM_WIN32_WINDOWS) { + + /* Win9x build */ + + ngx_log_error(NGX_LOG_INFO, log, 0, + "OS: %ui build:%ud.%ud.%ud, \"%s\"", + ngx_win32_version, + osvi.dwBuildNumber >> 24, + (osvi.dwBuildNumber >> 16) & 0xff, + osvi.dwBuildNumber & 0xffff, + osvi.szCSDVersion); + + } else { + + /* + * VER_PLATFORM_WIN32_NT + * + * we do not currently support VER_PLATFORM_WIN32_CE + * and we do not support VER_PLATFORM_WIN32s at all + */ + + ngx_log_error(NGX_LOG_INFO, log, 0, "OS: %ui build:%ud, \"%s\"", + ngx_win32_version, osvi.dwBuildNumber, + osvi.szCSDVersion); + } + } +} diff --git a/src/os/win32/ngx_wsarecv.c b/src/os/win32/ngx_wsarecv.c new file mode 100644 index 0000000..b01405e --- /dev/null +++ b/src/os/win32/ngx_wsarecv.c @@ -0,0 +1,215 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> + + +ssize_t +ngx_wsarecv(ngx_connection_t *c, u_char *buf, size_t size) +{ + int rc; + u_long bytes, flags; + WSABUF wsabuf[1]; + ngx_err_t err; + ngx_int_t n; + ngx_event_t *rev; + + wsabuf[0].buf = (char *) buf; + wsabuf[0].len = size; + flags = 0; + bytes = 0; + + rc = WSARecv(c->fd, wsabuf, 1, &bytes, &flags, NULL, NULL); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: fd:%d rc:%d %ul of %z", c->fd, rc, bytes, size); + + rev = c->read; + + if (rc == -1) { + rev->ready = 0; + err = ngx_socket_errno; + + if (err == WSAEWOULDBLOCK) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSARecv() not ready"); + return NGX_AGAIN; + } + + n = ngx_connection_error(c, err, "WSARecv() failed"); + + if (n == NGX_ERROR) { + rev->error = 1; + } + + return n; + } + +#if (NGX_HAVE_FIONREAD) + + if (rev->available >= 0 && bytes > 0) { + rev->available -= bytes; + + /* + * negative rev->available means some additional bytes + * were received between kernel notification and WSARecv(), + * and therefore ev->ready can be safely reset even for + * edge-triggered event methods + */ + + if (rev->available < 0) { + rev->available = 0; + rev->ready = 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: avail:%d", rev->available); + + } else if (bytes == size) { + + if (ngx_socket_nread(c->fd, &rev->available) == -1) { + n = ngx_connection_error(c, ngx_socket_errno, + ngx_socket_nread_n " failed"); + + if (n == NGX_ERROR) { + rev->ready = 0; + rev->error = 1; + } + + return n; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: avail:%d", rev->available); + } + +#endif + + if (bytes < size) { + rev->ready = 0; + } + + if (bytes == 0) { + rev->ready = 0; + rev->eof = 1; + } + + return bytes; +} + + +ssize_t +ngx_overlapped_wsarecv(ngx_connection_t *c, u_char *buf, size_t size) +{ + int rc; + u_long bytes, flags; + WSABUF wsabuf[1]; + ngx_err_t err; + ngx_int_t n; + ngx_event_t *rev; + LPWSAOVERLAPPED ovlp; + + rev = c->read; + + if (!rev->ready) { + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "second wsa post"); + return NGX_AGAIN; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "rev->complete: %d", rev->complete); + + if (rev->complete) { + rev->complete = 0; + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + if (rev->ovlp.error) { + ngx_connection_error(c, rev->ovlp.error, "WSARecv() failed"); + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv ovlp: fd:%d %ul of %z", + c->fd, rev->available, size); + + return rev->available; + } + + if (WSAGetOverlappedResult(c->fd, (LPWSAOVERLAPPED) &rev->ovlp, + &bytes, 0, NULL) + == 0) + { + ngx_connection_error(c, ngx_socket_errno, + "WSARecv() or WSAGetOverlappedResult() failed"); + return NGX_ERROR; + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: fd:%d %ul of %z", c->fd, bytes, size); + + return bytes; + } + + ovlp = (LPWSAOVERLAPPED) &rev->ovlp; + ngx_memzero(ovlp, sizeof(WSAOVERLAPPED)); + wsabuf[0].buf = (char *) buf; + wsabuf[0].len = size; + flags = 0; + bytes = 0; + + rc = WSARecv(c->fd, wsabuf, 1, &bytes, &flags, ovlp, NULL); + + rev->complete = 0; + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv ovlp: fd:%d rc:%d %ul of %z", + c->fd, rc, bytes, size); + + if (rc == -1) { + err = ngx_socket_errno; + if (err == WSA_IO_PENDING) { + rev->active = 1; + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSARecv() posted"); + return NGX_AGAIN; + } + + n = ngx_connection_error(c, err, "WSARecv() failed"); + + if (n == NGX_ERROR) { + rev->error = 1; + } + + return n; + } + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + + /* + * if a socket was bound with I/O completion port + * then GetQueuedCompletionStatus() would anyway return its status + * despite that WSARecv() was already complete + */ + + rev->active = 1; + return NGX_AGAIN; + } + + if (bytes == 0) { + rev->eof = 1; + rev->ready = 0; + + } else { + rev->ready = 1; + } + + rev->active = 0; + + return bytes; +} diff --git a/src/os/win32/ngx_wsarecv_chain.c b/src/os/win32/ngx_wsarecv_chain.c new file mode 100644 index 0000000..e60389b --- /dev/null +++ b/src/os/win32/ngx_wsarecv_chain.c @@ -0,0 +1,147 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> + + +#define NGX_WSABUFS 64 + + +ssize_t +ngx_wsarecv_chain(ngx_connection_t *c, ngx_chain_t *chain, off_t limit) +{ + int rc; + u_char *prev; + u_long bytes, flags; + size_t n, size; + ngx_err_t err; + ngx_array_t vec; + ngx_event_t *rev; + LPWSABUF wsabuf; + WSABUF wsabufs[NGX_WSABUFS]; + + prev = NULL; + wsabuf = NULL; + flags = 0; + size = 0; + bytes = 0; + + vec.elts = wsabufs; + vec.nelts = 0; + vec.size = sizeof(WSABUF); + vec.nalloc = NGX_WSABUFS; + vec.pool = c->pool; + + /* coalesce the neighbouring bufs */ + + while (chain) { + n = chain->buf->end - chain->buf->last; + + if (limit) { + if (size >= (size_t) limit) { + break; + } + + if (size + n > (size_t) limit) { + n = (size_t) limit - size; + } + } + + if (prev == chain->buf->last) { + wsabuf->len += n; + + } else { + if (vec.nelts == vec.nalloc) { + break; + } + + wsabuf = ngx_array_push(&vec); + if (wsabuf == NULL) { + return NGX_ERROR; + } + + wsabuf->buf = (char *) chain->buf->last; + wsabuf->len = n; + } + + size += n; + prev = chain->buf->end; + chain = chain->next; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: %d:%d", vec.nelts, wsabuf->len); + + + rc = WSARecv(c->fd, vec.elts, vec.nelts, &bytes, &flags, NULL, NULL); + + rev = c->read; + + if (rc == -1) { + rev->ready = 0; + err = ngx_socket_errno; + + if (err == WSAEWOULDBLOCK) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSARecv() not ready"); + return NGX_AGAIN; + } + + rev->error = 1; + ngx_connection_error(c, err, "WSARecv() failed"); + return NGX_ERROR; + } + +#if (NGX_HAVE_FIONREAD) + + if (rev->available >= 0 && bytes > 0) { + rev->available -= bytes; + + /* + * negative rev->available means some additional bytes + * were received between kernel notification and WSARecv(), + * and therefore ev->ready can be safely reset even for + * edge-triggered event methods + */ + + if (rev->available < 0) { + rev->available = 0; + rev->ready = 0; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: avail:%d", rev->available); + + } else if (bytes == size) { + + if (ngx_socket_nread(c->fd, &rev->available) == -1) { + rev->ready = 0; + rev->error = 1; + ngx_connection_error(c, ngx_socket_errno, + ngx_socket_nread_n " failed"); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSARecv: avail:%d", rev->available); + } + +#endif + + if (bytes < size) { + rev->ready = 0; + } + + if (bytes == 0) { + rev->ready = 0; + rev->eof = 1; + } + + return bytes; +} diff --git a/src/os/win32/ngx_wsasend.c b/src/os/win32/ngx_wsasend.c new file mode 100644 index 0000000..d6a23d1 --- /dev/null +++ b/src/os/win32/ngx_wsasend.c @@ -0,0 +1,185 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> + + +ssize_t +ngx_wsasend(ngx_connection_t *c, u_char *buf, size_t size) +{ + int n; + u_long sent; + ngx_err_t err; + ngx_event_t *wev; + WSABUF wsabuf; + + wev = c->write; + + if (!wev->ready) { + return NGX_AGAIN; + } + + /* + * WSABUF must be 4-byte aligned otherwise + * WSASend() will return undocumented WSAEINVAL error. + */ + + wsabuf.buf = (char *) buf; + wsabuf.len = size; + + sent = 0; + + n = WSASend(c->fd, &wsabuf, 1, &sent, 0, NULL, NULL); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSASend: fd:%d, %d, %ul of %uz", c->fd, n, sent, size); + + if (n == 0) { + if (sent < size) { + wev->ready = 0; + } + + c->sent += sent; + + return sent; + } + + err = ngx_socket_errno; + + if (err == WSAEWOULDBLOCK) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, "WSASend() not ready"); + wev->ready = 0; + return NGX_AGAIN; + } + + wev->error = 1; + ngx_connection_error(c, err, "WSASend() failed"); + + return NGX_ERROR; +} + + +ssize_t +ngx_overlapped_wsasend(ngx_connection_t *c, u_char *buf, size_t size) +{ + int n; + u_long sent; + ngx_err_t err; + ngx_event_t *wev; + LPWSAOVERLAPPED ovlp; + WSABUF wsabuf; + + wev = c->write; + + if (!wev->ready) { + return NGX_AGAIN; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "wev->complete: %d", wev->complete); + + if (!wev->complete) { + + /* post the overlapped WSASend() */ + + /* + * WSABUFs must be 4-byte aligned otherwise + * WSASend() will return undocumented WSAEINVAL error. + */ + + wsabuf.buf = (char *) buf; + wsabuf.len = size; + + sent = 0; + + ovlp = (LPWSAOVERLAPPED) &c->write->ovlp; + ngx_memzero(ovlp, sizeof(WSAOVERLAPPED)); + + n = WSASend(c->fd, &wsabuf, 1, &sent, 0, ovlp, NULL); + + ngx_log_debug4(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSASend: fd:%d, %d, %ul of %uz", c->fd, n, sent, size); + + wev->complete = 0; + + if (n == 0) { + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + + /* + * if a socket was bound with I/O completion port then + * GetQueuedCompletionStatus() would anyway return its status + * despite that WSASend() was already complete + */ + + wev->active = 1; + return NGX_AGAIN; + } + + if (sent < size) { + wev->ready = 0; + } + + c->sent += sent; + + return sent; + } + + err = ngx_socket_errno; + + if (err == WSA_IO_PENDING) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSASend() posted"); + wev->active = 1; + return NGX_AGAIN; + } + + wev->error = 1; + ngx_connection_error(c, err, "WSASend() failed"); + + return NGX_ERROR; + } + + /* the overlapped WSASend() complete */ + + wev->complete = 0; + wev->active = 0; + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + + if (wev->ovlp.error) { + ngx_connection_error(c, wev->ovlp.error, "WSASend() failed"); + return NGX_ERROR; + } + + sent = wev->available; + + } else { + if (WSAGetOverlappedResult(c->fd, (LPWSAOVERLAPPED) &wev->ovlp, + &sent, 0, NULL) + == 0) + { + ngx_connection_error(c, ngx_socket_errno, + "WSASend() or WSAGetOverlappedResult() failed"); + + return NGX_ERROR; + } + } + + ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSAGetOverlappedResult: fd:%d, %ul of %uz", + c->fd, sent, size); + + if (sent < size) { + wev->ready = 0; + } + + c->sent += sent; + + return sent; +} diff --git a/src/os/win32/ngx_wsasend_chain.c b/src/os/win32/ngx_wsasend_chain.c new file mode 100644 index 0000000..cd50e71 --- /dev/null +++ b/src/os/win32/ngx_wsasend_chain.c @@ -0,0 +1,296 @@ + +/* + * Copyright (C) Igor Sysoev + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_event.h> + + +#define NGX_WSABUFS 64 + + +ngx_chain_t * +ngx_wsasend_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + int rc; + u_char *prev; + u_long size, sent, send, prev_send; + ngx_err_t err; + ngx_event_t *wev; + ngx_array_t vec; + ngx_chain_t *cl; + LPWSABUF wsabuf; + WSABUF wsabufs[NGX_WSABUFS]; + + wev = c->write; + + if (!wev->ready) { + return in; + } + + /* the maximum limit size is the maximum u_long value - the page size */ + + if (limit == 0 || limit > (off_t) (NGX_MAX_UINT32_VALUE - ngx_pagesize)) { + limit = NGX_MAX_UINT32_VALUE - ngx_pagesize; + } + + send = 0; + + /* + * WSABUFs must be 4-byte aligned otherwise + * WSASend() will return undocumented WSAEINVAL error. + */ + + vec.elts = wsabufs; + vec.size = sizeof(WSABUF); + vec.nalloc = ngx_min(NGX_WSABUFS, ngx_max_wsabufs); + vec.pool = c->pool; + + for ( ;; ) { + prev = NULL; + wsabuf = NULL; + prev_send = send; + + vec.nelts = 0; + + /* create the WSABUF and coalesce the neighbouring bufs */ + + for (cl = in; cl && send < limit; cl = cl->next) { + + if (ngx_buf_special(cl->buf)) { + continue; + } + + size = cl->buf->last - cl->buf->pos; + + if (send + size > limit) { + size = (u_long) (limit - send); + } + + if (prev == cl->buf->pos) { + wsabuf->len += cl->buf->last - cl->buf->pos; + + } else { + if (vec.nelts == vec.nalloc) { + break; + } + + wsabuf = ngx_array_push(&vec); + if (wsabuf == NULL) { + return NGX_CHAIN_ERROR; + } + + wsabuf->buf = (char *) cl->buf->pos; + wsabuf->len = cl->buf->last - cl->buf->pos; + } + + prev = cl->buf->last; + send += size; + } + + sent = 0; + + rc = WSASend(c->fd, vec.elts, vec.nelts, &sent, 0, NULL, NULL); + + if (rc == -1) { + err = ngx_errno; + + if (err == WSAEWOULDBLOCK) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSASend() not ready"); + + } else { + wev->error = 1; + ngx_connection_error(c, err, "WSASend() failed"); + return NGX_CHAIN_ERROR; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSASend: fd:%d, s:%ul", c->fd, sent); + + c->sent += sent; + + in = ngx_chain_update_sent(in, sent); + + if (send - prev_send != sent) { + wev->ready = 0; + return in; + } + + if (send >= limit || in == NULL) { + return in; + } + } +} + + +ngx_chain_t * +ngx_overlapped_wsasend_chain(ngx_connection_t *c, ngx_chain_t *in, off_t limit) +{ + int rc; + u_char *prev; + u_long size, send, sent; + ngx_err_t err; + ngx_event_t *wev; + ngx_array_t vec; + ngx_chain_t *cl; + LPWSAOVERLAPPED ovlp; + LPWSABUF wsabuf; + WSABUF wsabufs[NGX_WSABUFS]; + + wev = c->write; + + if (!wev->ready) { + return in; + } + + ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, + "wev->complete: %d", wev->complete); + + if (!wev->complete) { + + /* post the overlapped WSASend() */ + + /* the maximum limit size is the maximum u_long value - the page size */ + + if (limit == 0 || limit > (off_t) (NGX_MAX_UINT32_VALUE - ngx_pagesize)) + { + limit = NGX_MAX_UINT32_VALUE - ngx_pagesize; + } + + /* + * WSABUFs must be 4-byte aligned otherwise + * WSASend() will return undocumented WSAEINVAL error. + */ + + vec.elts = wsabufs; + vec.nelts = 0; + vec.size = sizeof(WSABUF); + vec.nalloc = ngx_min(NGX_WSABUFS, ngx_max_wsabufs); + vec.pool = c->pool; + + send = 0; + prev = NULL; + wsabuf = NULL; + + /* create the WSABUF and coalesce the neighbouring bufs */ + + for (cl = in; cl && send < limit; cl = cl->next) { + + if (ngx_buf_special(cl->buf)) { + continue; + } + + size = cl->buf->last - cl->buf->pos; + + if (send + size > limit) { + size = (u_long) (limit - send); + } + + if (prev == cl->buf->pos) { + wsabuf->len += cl->buf->last - cl->buf->pos; + + } else { + if (vec.nelts == vec.nalloc) { + break; + } + + wsabuf = ngx_array_push(&vec); + if (wsabuf == NULL) { + return NGX_CHAIN_ERROR; + } + + wsabuf->buf = (char *) cl->buf->pos; + wsabuf->len = cl->buf->last - cl->buf->pos; + } + + prev = cl->buf->last; + send += size; + } + + ovlp = (LPWSAOVERLAPPED) &c->write->ovlp; + ngx_memzero(ovlp, sizeof(WSAOVERLAPPED)); + + rc = WSASend(c->fd, vec.elts, vec.nelts, &sent, 0, ovlp, NULL); + + wev->complete = 0; + + if (rc == -1) { + err = ngx_errno; + + if (err == WSA_IO_PENDING) { + ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, err, + "WSASend() posted"); + wev->active = 1; + return in; + + } else { + wev->error = 1; + ngx_connection_error(c, err, "WSASend() failed"); + return NGX_CHAIN_ERROR; + } + + } else if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + + /* + * if a socket was bound with I/O completion port then + * GetQueuedCompletionStatus() would anyway return its status + * despite that WSASend() was already complete + */ + + wev->active = 1; + return in; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSASend: fd:%d, s:%ul", c->fd, sent); + + } else { + + /* the overlapped WSASend() complete */ + + wev->complete = 0; + wev->active = 0; + + if (ngx_event_flags & NGX_USE_IOCP_EVENT) { + if (wev->ovlp.error) { + ngx_connection_error(c, wev->ovlp.error, "WSASend() failed"); + return NGX_CHAIN_ERROR; + } + + sent = wev->available; + + } else { + if (WSAGetOverlappedResult(c->fd, (LPWSAOVERLAPPED) &wev->ovlp, + &sent, 0, NULL) + == 0) + { + ngx_connection_error(c, ngx_socket_errno, + "WSASend() or WSAGetOverlappedResult() failed"); + + return NGX_CHAIN_ERROR; + } + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, c->log, 0, + "WSASend ovlp: fd:%d, s:%ul", c->fd, sent); + + c->sent += sent; + + in = ngx_chain_update_sent(in, sent); + + if (in) { + wev->ready = 0; + + } else { + wev->ready = 1; + } + + return in; +} diff --git a/src/stream/ngx_stream.c b/src/stream/ngx_stream.c index 3304c84..b6eeb23 100644 --- a/src/stream/ngx_stream.c +++ b/src/stream/ngx_stream.c @@ -16,16 +16,34 @@ static ngx_int_t ngx_stream_init_phases(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf); static ngx_int_t ngx_stream_init_phase_handlers(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf); -static ngx_int_t ngx_stream_add_ports(ngx_conf_t *cf, ngx_array_t *ports, - ngx_stream_listen_t *listen); -static char *ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports); + +static ngx_int_t ngx_stream_add_addresses(ngx_conf_t *cf, + ngx_stream_core_srv_conf_t *cscf, ngx_stream_conf_port_t *port, + ngx_stream_listen_opt_t *lsopt); +static ngx_int_t ngx_stream_add_address(ngx_conf_t *cf, + ngx_stream_core_srv_conf_t *cscf, ngx_stream_conf_port_t *port, + ngx_stream_listen_opt_t *lsopt); +static ngx_int_t ngx_stream_add_server(ngx_conf_t *cf, + ngx_stream_core_srv_conf_t *cscf, ngx_stream_conf_addr_t *addr); + +static ngx_int_t ngx_stream_optimize_servers(ngx_conf_t *cf, + ngx_stream_core_main_conf_t *cmcf, ngx_array_t *ports); +static ngx_int_t ngx_stream_server_names(ngx_conf_t *cf, + ngx_stream_core_main_conf_t *cmcf, ngx_stream_conf_addr_t *addr); +static ngx_int_t ngx_stream_cmp_conf_addrs(const void *one, const void *two); +static int ngx_libc_cdecl ngx_stream_cmp_dns_wildcards(const void *one, + const void *two); + +static ngx_int_t ngx_stream_init_listening(ngx_conf_t *cf, + ngx_stream_conf_port_t *port); +static ngx_listening_t *ngx_stream_add_listening(ngx_conf_t *cf, + ngx_stream_conf_addr_t *addr); static ngx_int_t ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport, ngx_stream_conf_addr_t *addr); #if (NGX_HAVE_INET6) static ngx_int_t ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport, ngx_stream_conf_addr_t *addr); #endif -static ngx_int_t ngx_stream_cmp_conf_addrs(const void *one, const void *two); ngx_uint_t ngx_stream_max_module; @@ -74,10 +92,8 @@ static char * ngx_stream_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { char *rv; - ngx_uint_t i, m, mi, s; + ngx_uint_t mi, m, s; ngx_conf_t pcf; - ngx_array_t ports; - ngx_stream_listen_t *listen; ngx_stream_module_t *module; ngx_stream_conf_ctx_t *ctx; ngx_stream_core_srv_conf_t **cscfp; @@ -251,21 +267,13 @@ ngx_stream_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - if (ngx_array_init(&ports, cf->temp_pool, 4, sizeof(ngx_stream_conf_port_t)) - != NGX_OK) - { - return NGX_CONF_ERROR; - } - - listen = cmcf->listen.elts; + /* optimize the lists of ports, addresses and server names */ - for (i = 0; i < cmcf->listen.nelts; i++) { - if (ngx_stream_add_ports(cf, &ports, &listen[i]) != NGX_OK) { - return NGX_CONF_ERROR; - } + if (ngx_stream_optimize_servers(cf, cmcf, cmcf->ports) != NGX_OK) { + return NGX_CONF_ERROR; } - return ngx_stream_optimize_servers(cf, &ports); + return NGX_CONF_OK; } @@ -377,73 +385,295 @@ ngx_stream_init_phase_handlers(ngx_conf_t *cf, } -static ngx_int_t -ngx_stream_add_ports(ngx_conf_t *cf, ngx_array_t *ports, - ngx_stream_listen_t *listen) +ngx_int_t +ngx_stream_add_listen(ngx_conf_t *cf, ngx_stream_core_srv_conf_t *cscf, + ngx_stream_listen_opt_t *lsopt) { - in_port_t p; - ngx_uint_t i; - struct sockaddr *sa; - ngx_stream_conf_port_t *port; - ngx_stream_conf_addr_t *addr; + in_port_t p; + ngx_uint_t i; + struct sockaddr *sa; + ngx_stream_conf_port_t *port; + ngx_stream_core_main_conf_t *cmcf; + + cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); + + if (cmcf->ports == NULL) { + cmcf->ports = ngx_array_create(cf->temp_pool, 2, + sizeof(ngx_stream_conf_port_t)); + if (cmcf->ports == NULL) { + return NGX_ERROR; + } + } - sa = listen->sockaddr; + sa = lsopt->sockaddr; p = ngx_inet_get_port(sa); - port = ports->elts; - for (i = 0; i < ports->nelts; i++) { + port = cmcf->ports->elts; + for (i = 0; i < cmcf->ports->nelts; i++) { - if (p == port[i].port - && listen->type == port[i].type - && sa->sa_family == port[i].family) + if (p != port[i].port + || lsopt->type != port[i].type + || sa->sa_family != port[i].family) { - /* a port is already in the port list */ - - port = &port[i]; - goto found; + continue; } + + /* a port is already in the port list */ + + return ngx_stream_add_addresses(cf, cscf, &port[i], lsopt); } /* add a port to the port list */ - port = ngx_array_push(ports); + port = ngx_array_push(cmcf->ports); if (port == NULL) { return NGX_ERROR; } port->family = sa->sa_family; - port->type = listen->type; + port->type = lsopt->type; port->port = p; + port->addrs.elts = NULL; - if (ngx_array_init(&port->addrs, cf->temp_pool, 2, - sizeof(ngx_stream_conf_addr_t)) - != NGX_OK) - { - return NGX_ERROR; + return ngx_stream_add_address(cf, cscf, port, lsopt); +} + + +static ngx_int_t +ngx_stream_add_addresses(ngx_conf_t *cf, ngx_stream_core_srv_conf_t *cscf, + ngx_stream_conf_port_t *port, ngx_stream_listen_opt_t *lsopt) +{ + ngx_uint_t i, default_server, proxy_protocol, + protocols, protocols_prev; + ngx_stream_conf_addr_t *addr; +#if (NGX_STREAM_SSL) + ngx_uint_t ssl; +#endif + + /* + * we cannot compare whole sockaddr struct's as kernel + * may fill some fields in inherited sockaddr struct's + */ + + addr = port->addrs.elts; + + for (i = 0; i < port->addrs.nelts; i++) { + + if (ngx_cmp_sockaddr(lsopt->sockaddr, lsopt->socklen, + addr[i].opt.sockaddr, + addr[i].opt.socklen, 0) + != NGX_OK) + { + continue; + } + + /* the address is already in the address list */ + + if (ngx_stream_add_server(cf, cscf, &addr[i]) != NGX_OK) { + return NGX_ERROR; + } + + /* preserve default_server bit during listen options overwriting */ + default_server = addr[i].opt.default_server; + + proxy_protocol = lsopt->proxy_protocol || addr[i].opt.proxy_protocol; + protocols = lsopt->proxy_protocol; + protocols_prev = addr[i].opt.proxy_protocol; + +#if (NGX_STREAM_SSL) + ssl = lsopt->ssl || addr[i].opt.ssl; + protocols |= lsopt->ssl << 1; + protocols_prev |= addr[i].opt.ssl << 1; +#endif + + if (lsopt->set) { + + if (addr[i].opt.set) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "duplicate listen options for %V", + &addr[i].opt.addr_text); + return NGX_ERROR; + } + + addr[i].opt = *lsopt; + } + + /* check the duplicate "default" server for this address:port */ + + if (lsopt->default_server) { + + if (default_server) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "a duplicate default server for %V", + &addr[i].opt.addr_text); + return NGX_ERROR; + } + + default_server = 1; + addr[i].default_server = cscf; + } + + /* check for conflicting protocol options */ + + if ((protocols | protocols_prev) != protocols_prev) { + + /* options added */ + + if ((addr[i].opt.set && !lsopt->set) + || addr[i].protocols_changed + || (protocols | protocols_prev) != protocols) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "protocol options redefined for %V", + &addr[i].opt.addr_text); + } + + addr[i].protocols = protocols_prev; + addr[i].protocols_set = 1; + addr[i].protocols_changed = 1; + + } else if ((protocols_prev | protocols) != protocols) { + + /* options removed */ + + if (lsopt->set + || (addr[i].protocols_set && protocols != addr[i].protocols)) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "protocol options redefined for %V", + &addr[i].opt.addr_text); + } + + addr[i].protocols = protocols; + addr[i].protocols_set = 1; + addr[i].protocols_changed = 1; + + } else { + + /* the same options */ + + if ((lsopt->set && addr[i].protocols_changed) + || (addr[i].protocols_set && protocols != addr[i].protocols)) + { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "protocol options redefined for %V", + &addr[i].opt.addr_text); + } + + addr[i].protocols = protocols; + addr[i].protocols_set = 1; + } + + addr[i].opt.default_server = default_server; + addr[i].opt.proxy_protocol = proxy_protocol; +#if (NGX_STREAM_SSL) + addr[i].opt.ssl = ssl; +#endif + + return NGX_OK; } -found: + /* add the address to the addresses list that bound to this port */ + + return ngx_stream_add_address(cf, cscf, port, lsopt); +} + + +/* + * add the server address, the server names and the server core module + * configurations to the port list + */ + +static ngx_int_t +ngx_stream_add_address(ngx_conf_t *cf, ngx_stream_core_srv_conf_t *cscf, + ngx_stream_conf_port_t *port, ngx_stream_listen_opt_t *lsopt) +{ + ngx_stream_conf_addr_t *addr; + + if (port->addrs.elts == NULL) { + if (ngx_array_init(&port->addrs, cf->temp_pool, 4, + sizeof(ngx_stream_conf_addr_t)) + != NGX_OK) + { + return NGX_ERROR; + } + } addr = ngx_array_push(&port->addrs); if (addr == NULL) { return NGX_ERROR; } - addr->opt = *listen; + addr->opt = *lsopt; + addr->protocols = 0; + addr->protocols_set = 0; + addr->protocols_changed = 0; + addr->hash.buckets = NULL; + addr->hash.size = 0; + addr->wc_head = NULL; + addr->wc_tail = NULL; +#if (NGX_PCRE) + addr->nregex = 0; + addr->regex = NULL; +#endif + addr->default_server = cscf; + addr->servers.elts = NULL; + + return ngx_stream_add_server(cf, cscf, addr); +} + + +/* add the server core module configuration to the address:port */ + +static ngx_int_t +ngx_stream_add_server(ngx_conf_t *cf, ngx_stream_core_srv_conf_t *cscf, + ngx_stream_conf_addr_t *addr) +{ + ngx_uint_t i; + ngx_stream_core_srv_conf_t **server; + + if (addr->servers.elts == NULL) { + if (ngx_array_init(&addr->servers, cf->temp_pool, 4, + sizeof(ngx_stream_core_srv_conf_t *)) + != NGX_OK) + { + return NGX_ERROR; + } + + } else { + server = addr->servers.elts; + for (i = 0; i < addr->servers.nelts; i++) { + if (server[i] == cscf) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "a duplicate listen %V", + &addr->opt.addr_text); + return NGX_ERROR; + } + } + } + + server = ngx_array_push(&addr->servers); + if (server == NULL) { + return NGX_ERROR; + } + + *server = cscf; return NGX_OK; } -static char * -ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports) +static ngx_int_t +ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf, + ngx_array_t *ports) { - ngx_uint_t i, p, last, bind_wildcard; - ngx_listening_t *ls; - ngx_stream_port_t *stport; - ngx_stream_conf_port_t *port; - ngx_stream_conf_addr_t *addr; - ngx_stream_core_srv_conf_t *cscf; + ngx_uint_t p, a; + ngx_stream_conf_port_t *port; + ngx_stream_conf_addr_t *addr; + + if (ports == NULL) { + return NGX_OK; + } port = ports->elts; for (p = 0; p < ports->nelts; p++) { @@ -451,103 +681,373 @@ ngx_stream_optimize_servers(ngx_conf_t *cf, ngx_array_t *ports) ngx_sort(port[p].addrs.elts, (size_t) port[p].addrs.nelts, sizeof(ngx_stream_conf_addr_t), ngx_stream_cmp_conf_addrs); - addr = port[p].addrs.elts; - last = port[p].addrs.nelts; - /* - * if there is the binding to the "*:port" then we need to bind() - * to the "*:port" only and ignore the other bindings + * check whether all name-based servers have the same + * configuration as a default server for given address:port */ - if (addr[last - 1].opt.wildcard) { - addr[last - 1].opt.bind = 1; - bind_wildcard = 1; + addr = port[p].addrs.elts; + for (a = 0; a < port[p].addrs.nelts; a++) { - } else { - bind_wildcard = 0; + if (addr[a].servers.nelts > 1 +#if (NGX_PCRE) + || addr[a].default_server->captures +#endif + ) + { + if (ngx_stream_server_names(cf, cmcf, &addr[a]) != NGX_OK) { + return NGX_ERROR; + } + } } - i = 0; + if (ngx_stream_init_listening(cf, &port[p]) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_server_names(ngx_conf_t *cf, ngx_stream_core_main_conf_t *cmcf, + ngx_stream_conf_addr_t *addr) +{ + ngx_int_t rc; + ngx_uint_t n, s; + ngx_hash_init_t hash; + ngx_hash_keys_arrays_t ha; + ngx_stream_server_name_t *name; + ngx_stream_core_srv_conf_t **cscfp; +#if (NGX_PCRE) + ngx_uint_t regex, i; + + regex = 0; +#endif + + ngx_memzero(&ha, sizeof(ngx_hash_keys_arrays_t)); - while (i < last) { + ha.temp_pool = ngx_create_pool(NGX_DEFAULT_POOL_SIZE, cf->log); + if (ha.temp_pool == NULL) { + return NGX_ERROR; + } + + ha.pool = cf->pool; + + if (ngx_hash_keys_array_init(&ha, NGX_HASH_LARGE) != NGX_OK) { + goto failed; + } + + cscfp = addr->servers.elts; - if (bind_wildcard && !addr[i].opt.bind) { - i++; + for (s = 0; s < addr->servers.nelts; s++) { + + name = cscfp[s]->server_names.elts; + + for (n = 0; n < cscfp[s]->server_names.nelts; n++) { + +#if (NGX_PCRE) + if (name[n].regex) { + regex++; continue; } +#endif - ls = ngx_create_listening(cf, addr[i].opt.sockaddr, - addr[i].opt.socklen); - if (ls == NULL) { - return NGX_CONF_ERROR; + rc = ngx_hash_add_key(&ha, &name[n].name, name[n].server, + NGX_HASH_WILDCARD_KEY); + + if (rc == NGX_ERROR) { + goto failed; } - ls->addr_ntop = 1; - ls->handler = ngx_stream_init_connection; - ls->pool_size = 256; - ls->type = addr[i].opt.type; + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "invalid server name or wildcard \"%V\" on %V", + &name[n].name, &addr->opt.addr_text); + goto failed; + } - cscf = addr->opt.ctx->srv_conf[ngx_stream_core_module.ctx_index]; + if (rc == NGX_BUSY) { + ngx_log_error(NGX_LOG_WARN, cf->log, 0, + "conflicting server name \"%V\" on %V, ignored", + &name[n].name, &addr->opt.addr_text); + } + } + } - ls->logp = cscf->error_log; - ls->log.data = &ls->addr_text; - ls->log.handler = ngx_accept_log_error; + hash.key = ngx_hash_key_lc; + hash.max_size = cmcf->server_names_hash_max_size; + hash.bucket_size = cmcf->server_names_hash_bucket_size; + hash.name = "server_names_hash"; + hash.pool = cf->pool; - ls->backlog = addr[i].opt.backlog; - ls->rcvbuf = addr[i].opt.rcvbuf; - ls->sndbuf = addr[i].opt.sndbuf; + if (ha.keys.nelts) { + hash.hash = &addr->hash; + hash.temp_pool = NULL; - ls->wildcard = addr[i].opt.wildcard; + if (ngx_hash_init(&hash, ha.keys.elts, ha.keys.nelts) != NGX_OK) { + goto failed; + } + } + + if (ha.dns_wc_head.nelts) { + + ngx_qsort(ha.dns_wc_head.elts, (size_t) ha.dns_wc_head.nelts, + sizeof(ngx_hash_key_t), ngx_stream_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = ha.temp_pool; + + if (ngx_hash_wildcard_init(&hash, ha.dns_wc_head.elts, + ha.dns_wc_head.nelts) + != NGX_OK) + { + goto failed; + } + + addr->wc_head = (ngx_hash_wildcard_t *) hash.hash; + } + + if (ha.dns_wc_tail.nelts) { + + ngx_qsort(ha.dns_wc_tail.elts, (size_t) ha.dns_wc_tail.nelts, + sizeof(ngx_hash_key_t), ngx_stream_cmp_dns_wildcards); + + hash.hash = NULL; + hash.temp_pool = ha.temp_pool; + + if (ngx_hash_wildcard_init(&hash, ha.dns_wc_tail.elts, + ha.dns_wc_tail.nelts) + != NGX_OK) + { + goto failed; + } + + addr->wc_tail = (ngx_hash_wildcard_t *) hash.hash; + } + + ngx_destroy_pool(ha.temp_pool); + +#if (NGX_PCRE) + + if (regex == 0) { + return NGX_OK; + } + + addr->nregex = regex; + addr->regex = ngx_palloc(cf->pool, + regex * sizeof(ngx_stream_server_name_t)); + if (addr->regex == NULL) { + return NGX_ERROR; + } + + i = 0; + + for (s = 0; s < addr->servers.nelts; s++) { + + name = cscfp[s]->server_names.elts; + + for (n = 0; n < cscfp[s]->server_names.nelts; n++) { + if (name[n].regex) { + addr->regex[i++] = name[n]; + } + } + } - ls->keepalive = addr[i].opt.so_keepalive; -#if (NGX_HAVE_KEEPALIVE_TUNABLE) - ls->keepidle = addr[i].opt.tcp_keepidle; - ls->keepintvl = addr[i].opt.tcp_keepintvl; - ls->keepcnt = addr[i].opt.tcp_keepcnt; #endif + return NGX_OK; + +failed: + + ngx_destroy_pool(ha.temp_pool); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_stream_cmp_conf_addrs(const void *one, const void *two) +{ + ngx_stream_conf_addr_t *first, *second; + + first = (ngx_stream_conf_addr_t *) one; + second = (ngx_stream_conf_addr_t *) two; + + if (first->opt.wildcard) { + /* a wildcard address must be the last resort, shift it to the end */ + return 1; + } + + if (second->opt.wildcard) { + /* a wildcard address must be the last resort, shift it to the end */ + return -1; + } + + if (first->opt.bind && !second->opt.bind) { + /* shift explicit bind()ed addresses to the start */ + return -1; + } + + if (!first->opt.bind && second->opt.bind) { + /* shift explicit bind()ed addresses to the start */ + return 1; + } + + /* do not sort by default */ + + return 0; +} + + +static int ngx_libc_cdecl +ngx_stream_cmp_dns_wildcards(const void *one, const void *two) +{ + ngx_hash_key_t *first, *second; + + first = (ngx_hash_key_t *) one; + second = (ngx_hash_key_t *) two; + + return ngx_dns_strcmp(first->key.data, second->key.data); +} + + +static ngx_int_t +ngx_stream_init_listening(ngx_conf_t *cf, ngx_stream_conf_port_t *port) +{ + ngx_uint_t i, last, bind_wildcard; + ngx_listening_t *ls; + ngx_stream_port_t *stport; + ngx_stream_conf_addr_t *addr; + + addr = port->addrs.elts; + last = port->addrs.nelts; + + /* + * If there is a binding to an "*:port" then we need to bind() to + * the "*:port" only and ignore other implicit bindings. The bindings + * have been already sorted: explicit bindings are on the start, then + * implicit bindings go, and wildcard binding is in the end. + */ + + if (addr[last - 1].opt.wildcard) { + addr[last - 1].opt.bind = 1; + bind_wildcard = 1; + + } else { + bind_wildcard = 0; + } + + i = 0; + + while (i < last) { + + if (bind_wildcard && !addr[i].opt.bind) { + i++; + continue; + } + + ls = ngx_stream_add_listening(cf, &addr[i]); + if (ls == NULL) { + return NGX_ERROR; + } + + stport = ngx_pcalloc(cf->pool, sizeof(ngx_stream_port_t)); + if (stport == NULL) { + return NGX_ERROR; + } + + ls->servers = stport; + + stport->naddrs = i + 1; + + switch (ls->sockaddr->sa_family) { + #if (NGX_HAVE_INET6) - ls->ipv6only = addr[i].opt.ipv6only; + case AF_INET6: + if (ngx_stream_add_addrs6(cf, stport, addr) != NGX_OK) { + return NGX_ERROR; + } + break; #endif + default: /* AF_INET */ + if (ngx_stream_add_addrs(cf, stport, addr) != NGX_OK) { + return NGX_ERROR; + } + break; + } -#if (NGX_HAVE_TCP_FASTOPEN) - ls->fastopen = addr[i].opt.fastopen; + addr++; + last--; + } + + return NGX_OK; +} + + +static ngx_listening_t * +ngx_stream_add_listening(ngx_conf_t *cf, ngx_stream_conf_addr_t *addr) +{ + ngx_listening_t *ls; + ngx_stream_core_srv_conf_t *cscf; + + ls = ngx_create_listening(cf, addr->opt.sockaddr, addr->opt.socklen); + if (ls == NULL) { + return NULL; + } + + ls->addr_ntop = 1; + + ls->handler = ngx_stream_init_connection; + + ls->pool_size = 256; + + cscf = addr->default_server; + + ls->logp = cscf->error_log; + ls->log.data = &ls->addr_text; + ls->log.handler = ngx_accept_log_error; + + ls->type = addr->opt.type; + ls->backlog = addr->opt.backlog; + ls->rcvbuf = addr->opt.rcvbuf; + ls->sndbuf = addr->opt.sndbuf; + + ls->keepalive = addr->opt.so_keepalive; +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + ls->keepidle = addr->opt.tcp_keepidle; + ls->keepintvl = addr->opt.tcp_keepintvl; + ls->keepcnt = addr->opt.tcp_keepcnt; #endif -#if (NGX_HAVE_REUSEPORT) - ls->reuseport = addr[i].opt.reuseport; +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + ls->accept_filter = addr->opt.accept_filter; #endif - stport = ngx_palloc(cf->pool, sizeof(ngx_stream_port_t)); - if (stport == NULL) { - return NGX_CONF_ERROR; - } +#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) + ls->deferred_accept = addr->opt.deferred_accept; +#endif - ls->servers = stport; +#if (NGX_HAVE_INET6) + ls->ipv6only = addr->opt.ipv6only; +#endif - stport->naddrs = i + 1; +#if (NGX_HAVE_SETFIB) + ls->setfib = addr->opt.setfib; +#endif - switch (ls->sockaddr->sa_family) { -#if (NGX_HAVE_INET6) - case AF_INET6: - if (ngx_stream_add_addrs6(cf, stport, addr) != NGX_OK) { - return NGX_CONF_ERROR; - } - break; +#if (NGX_HAVE_TCP_FASTOPEN) + ls->fastopen = addr->opt.fastopen; #endif - default: /* AF_INET */ - if (ngx_stream_add_addrs(cf, stport, addr) != NGX_OK) { - return NGX_CONF_ERROR; - } - break; - } - addr++; - last--; - } - } +#if (NGX_HAVE_REUSEPORT) + ls->reuseport = addr->opt.reuseport; +#endif - return NGX_CONF_OK; + ls->wildcard = addr->opt.wildcard; + + return ls; } @@ -555,9 +1055,10 @@ static ngx_int_t ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport, ngx_stream_conf_addr_t *addr) { - ngx_uint_t i; - struct sockaddr_in *sin; - ngx_stream_in_addr_t *addrs; + ngx_uint_t i; + struct sockaddr_in *sin; + ngx_stream_in_addr_t *addrs; + ngx_stream_virtual_names_t *vn; stport->addrs = ngx_pcalloc(cf->pool, stport->naddrs * sizeof(ngx_stream_in_addr_t)); @@ -571,13 +1072,39 @@ ngx_stream_add_addrs(ngx_conf_t *cf, ngx_stream_port_t *stport, sin = (struct sockaddr_in *) addr[i].opt.sockaddr; addrs[i].addr = sin->sin_addr.s_addr; - - addrs[i].conf.ctx = addr[i].opt.ctx; + addrs[i].conf.default_server = addr[i].default_server; #if (NGX_STREAM_SSL) addrs[i].conf.ssl = addr[i].opt.ssl; #endif addrs[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; - addrs[i].conf.addr_text = addr[i].opt.addr_text; + + if (addr[i].hash.buckets == NULL + && (addr[i].wc_head == NULL + || addr[i].wc_head->hash.buckets == NULL) + && (addr[i].wc_tail == NULL + || addr[i].wc_tail->hash.buckets == NULL) +#if (NGX_PCRE) + && addr[i].nregex == 0 +#endif + ) + { + continue; + } + + vn = ngx_palloc(cf->pool, sizeof(ngx_stream_virtual_names_t)); + if (vn == NULL) { + return NGX_ERROR; + } + + addrs[i].conf.virtual_names = vn; + + vn->names.hash = addr[i].hash; + vn->names.wc_head = addr[i].wc_head; + vn->names.wc_tail = addr[i].wc_tail; +#if (NGX_PCRE) + vn->nregex = addr[i].nregex; + vn->regex = addr[i].regex; +#endif } return NGX_OK; @@ -590,9 +1117,10 @@ static ngx_int_t ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport, ngx_stream_conf_addr_t *addr) { - ngx_uint_t i; - struct sockaddr_in6 *sin6; - ngx_stream_in6_addr_t *addrs6; + ngx_uint_t i; + struct sockaddr_in6 *sin6; + ngx_stream_in6_addr_t *addrs6; + ngx_stream_virtual_names_t *vn; stport->addrs = ngx_pcalloc(cf->pool, stport->naddrs * sizeof(ngx_stream_in6_addr_t)); @@ -606,50 +1134,42 @@ ngx_stream_add_addrs6(ngx_conf_t *cf, ngx_stream_port_t *stport, sin6 = (struct sockaddr_in6 *) addr[i].opt.sockaddr; addrs6[i].addr6 = sin6->sin6_addr; - - addrs6[i].conf.ctx = addr[i].opt.ctx; + addrs6[i].conf.default_server = addr[i].default_server; #if (NGX_STREAM_SSL) addrs6[i].conf.ssl = addr[i].opt.ssl; #endif addrs6[i].conf.proxy_protocol = addr[i].opt.proxy_protocol; - addrs6[i].conf.addr_text = addr[i].opt.addr_text; - } - - return NGX_OK; -} + if (addr[i].hash.buckets == NULL + && (addr[i].wc_head == NULL + || addr[i].wc_head->hash.buckets == NULL) + && (addr[i].wc_tail == NULL + || addr[i].wc_tail->hash.buckets == NULL) +#if (NGX_PCRE) + && addr[i].nregex == 0 #endif + ) + { + continue; + } + vn = ngx_palloc(cf->pool, sizeof(ngx_stream_virtual_names_t)); + if (vn == NULL) { + return NGX_ERROR; + } -static ngx_int_t -ngx_stream_cmp_conf_addrs(const void *one, const void *two) -{ - ngx_stream_conf_addr_t *first, *second; - - first = (ngx_stream_conf_addr_t *) one; - second = (ngx_stream_conf_addr_t *) two; - - if (first->opt.wildcard) { - /* a wildcard must be the last resort, shift it to the end */ - return 1; - } - - if (second->opt.wildcard) { - /* a wildcard must be the last resort, shift it to the end */ - return -1; - } - - if (first->opt.bind && !second->opt.bind) { - /* shift explicit bind()ed addresses to the start */ - return -1; - } + addrs6[i].conf.virtual_names = vn; - if (!first->opt.bind && second->opt.bind) { - /* shift explicit bind()ed addresses to the start */ - return 1; + vn->names.hash = addr[i].hash; + vn->names.wc_head = addr[i].wc_head; + vn->names.wc_tail = addr[i].wc_tail; +#if (NGX_PCRE) + vn->nregex = addr[i].nregex; + vn->regex = addr[i].regex; +#endif } - /* do not sort by default */ - - return 0; + return NGX_OK; } + +#endif diff --git a/src/stream/ngx_stream.h b/src/stream/ngx_stream.h index 46c3622..dc05dc5 100644 --- a/src/stream/ngx_stream.h +++ b/src/stream/ngx_stream.h @@ -45,74 +45,39 @@ typedef struct { socklen_t socklen; ngx_str_t addr_text; - /* server ctx */ - ngx_stream_conf_ctx_t *ctx; - + unsigned set:1; + unsigned default_server:1; unsigned bind:1; unsigned wildcard:1; unsigned ssl:1; #if (NGX_HAVE_INET6) unsigned ipv6only:1; #endif + unsigned deferred_accept:1; unsigned reuseport:1; unsigned so_keepalive:2; unsigned proxy_protocol:1; -#if (NGX_HAVE_KEEPALIVE_TUNABLE) - int tcp_keepidle; - int tcp_keepintvl; - int tcp_keepcnt; -#endif + int backlog; int rcvbuf; int sndbuf; + int type; +#if (NGX_HAVE_SETFIB) + int setfib; +#endif #if (NGX_HAVE_TCP_FASTOPEN) int fastopen; #endif - int type; -} ngx_stream_listen_t; - - -typedef struct { - ngx_stream_conf_ctx_t *ctx; - ngx_str_t addr_text; - unsigned ssl:1; - unsigned proxy_protocol:1; -} ngx_stream_addr_conf_t; - -typedef struct { - in_addr_t addr; - ngx_stream_addr_conf_t conf; -} ngx_stream_in_addr_t; - - -#if (NGX_HAVE_INET6) - -typedef struct { - struct in6_addr addr6; - ngx_stream_addr_conf_t conf; -} ngx_stream_in6_addr_t; - +#if (NGX_HAVE_KEEPALIVE_TUNABLE) + int tcp_keepidle; + int tcp_keepintvl; + int tcp_keepcnt; #endif - -typedef struct { - /* ngx_stream_in_addr_t or ngx_stream_in6_addr_t */ - void *addrs; - ngx_uint_t naddrs; -} ngx_stream_port_t; - - -typedef struct { - int family; - int type; - in_port_t port; - ngx_array_t addrs; /* array of ngx_stream_conf_addr_t */ -} ngx_stream_conf_port_t; - - -typedef struct { - ngx_stream_listen_t opt; -} ngx_stream_conf_addr_t; +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + char *accept_filter; +#endif +} ngx_stream_listen_opt_t; typedef enum { @@ -153,7 +118,6 @@ typedef struct { typedef struct { ngx_array_t servers; /* ngx_stream_core_srv_conf_t */ - ngx_array_t listen; /* ngx_stream_listen_t */ ngx_stream_phase_engine_t phase_engine; @@ -163,16 +127,24 @@ typedef struct { ngx_array_t prefix_variables; /* ngx_stream_variable_t */ ngx_uint_t ncaptures; + ngx_uint_t server_names_hash_max_size; + ngx_uint_t server_names_hash_bucket_size; + ngx_uint_t variables_hash_max_size; ngx_uint_t variables_hash_bucket_size; ngx_hash_keys_arrays_t *variables_keys; + ngx_array_t *ports; + ngx_stream_phase_t phases[NGX_STREAM_LOG_PHASE + 1]; } ngx_stream_core_main_conf_t; typedef struct { + /* array of the ngx_stream_server_name_t, "server_name" directive */ + ngx_array_t server_names; + ngx_stream_content_handler_pt handler; ngx_stream_conf_ctx_t *ctx; @@ -180,6 +152,8 @@ typedef struct { u_char *file_name; ngx_uint_t line; + ngx_str_t server_name; + ngx_flag_t tcp_nodelay; size_t preread_buffer_size; ngx_msec_t preread_timeout; @@ -191,10 +165,98 @@ typedef struct { ngx_msec_t proxy_protocol_timeout; - ngx_uint_t listen; /* unsigned listen:1; */ + unsigned listen:1; +#if (NGX_PCRE) + unsigned captures:1; +#endif } ngx_stream_core_srv_conf_t; +/* list of structures to find core_srv_conf quickly at run time */ + + +typedef struct { +#if (NGX_PCRE) + ngx_stream_regex_t *regex; +#endif + ngx_stream_core_srv_conf_t *server; /* virtual name server conf */ + ngx_str_t name; +} ngx_stream_server_name_t; + + +typedef struct { + ngx_hash_combined_t names; + + ngx_uint_t nregex; + ngx_stream_server_name_t *regex; +} ngx_stream_virtual_names_t; + + +typedef struct { + /* the default server configuration for this address:port */ + ngx_stream_core_srv_conf_t *default_server; + + ngx_stream_virtual_names_t *virtual_names; + + unsigned ssl:1; + unsigned proxy_protocol:1; +} ngx_stream_addr_conf_t; + + +typedef struct { + in_addr_t addr; + ngx_stream_addr_conf_t conf; +} ngx_stream_in_addr_t; + + +#if (NGX_HAVE_INET6) + +typedef struct { + struct in6_addr addr6; + ngx_stream_addr_conf_t conf; +} ngx_stream_in6_addr_t; + +#endif + + +typedef struct { + /* ngx_stream_in_addr_t or ngx_stream_in6_addr_t */ + void *addrs; + ngx_uint_t naddrs; +} ngx_stream_port_t; + + +typedef struct { + int family; + int type; + in_port_t port; + ngx_array_t addrs; /* array of ngx_stream_conf_addr_t */ +} ngx_stream_conf_port_t; + + +typedef struct { + ngx_stream_listen_opt_t opt; + + unsigned protocols:3; + unsigned protocols_set:1; + unsigned protocols_changed:1; + + ngx_hash_t hash; + ngx_hash_wildcard_t *wc_head; + ngx_hash_wildcard_t *wc_tail; + +#if (NGX_PCRE) + ngx_uint_t nregex; + ngx_stream_server_name_t *regex; +#endif + + /* the default server configuration for this address:port */ + ngx_stream_core_srv_conf_t *default_server; + ngx_array_t servers; + /* array of ngx_stream_core_srv_conf_t */ +} ngx_stream_conf_addr_t; + + struct ngx_stream_session_s { uint32_t signature; /* "STRM" */ @@ -210,6 +272,8 @@ struct ngx_stream_session_s { void **main_conf; void **srv_conf; + ngx_stream_virtual_names_t *virtual_names; + ngx_stream_upstream_t *upstream; ngx_array_t *upstream_states; /* of ngx_stream_upstream_state_t */ @@ -283,6 +347,9 @@ typedef struct { #define NGX_STREAM_WRITE_BUFFERED 0x10 +ngx_int_t ngx_stream_add_listen(ngx_conf_t *cf, + ngx_stream_core_srv_conf_t *cscf, ngx_stream_listen_opt_t *lsopt); + void ngx_stream_core_run_phases(ngx_stream_session_t *s); ngx_int_t ngx_stream_core_generic_phase(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph); @@ -291,6 +358,10 @@ ngx_int_t ngx_stream_core_preread_phase(ngx_stream_session_t *s, ngx_int_t ngx_stream_core_content_phase(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph); +ngx_int_t ngx_stream_validate_host(ngx_str_t *host, ngx_pool_t *pool, + ngx_uint_t alloc); +ngx_int_t ngx_stream_find_virtual_server(ngx_stream_session_t *s, + ngx_str_t *host, ngx_stream_core_srv_conf_t **cscfp); void ngx_stream_init_connection(ngx_connection_t *c); void ngx_stream_session_handler(ngx_event_t *rev); diff --git a/src/stream/ngx_stream_access_module.c b/src/stream/ngx_stream_access_module.c index a3020d4..070c226 100644 --- a/src/stream/ngx_stream_access_module.c +++ b/src/stream/ngx_stream_access_module.c @@ -144,7 +144,7 @@ ngx_stream_access_handler(ngx_stream_session_t *s) p = sin6->sin6_addr.s6_addr; if (ascf->rules && IN6_IS_ADDR_V4MAPPED(&sin6->sin6_addr)) { - addr = p[12] << 24; + addr = (in_addr_t) p[12] << 24; addr += p[13] << 16; addr += p[14] << 8; addr += p[15]; diff --git a/src/stream/ngx_stream_core_module.c b/src/stream/ngx_stream_core_module.c index d96d27a..3093963 100644 --- a/src/stream/ngx_stream_core_module.c +++ b/src/stream/ngx_stream_core_module.c @@ -10,6 +10,11 @@ #include <ngx_stream.h> +static ngx_uint_t ngx_stream_preread_can_peek(ngx_connection_t *c); +static ngx_int_t ngx_stream_preread_peek(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph); +static ngx_int_t ngx_stream_preread(ngx_stream_session_t *s, + ngx_stream_phase_handler_t *ph); static ngx_int_t ngx_stream_core_preconfiguration(ngx_conf_t *cf); static void *ngx_stream_core_create_main_conf(ngx_conf_t *cf); static char *ngx_stream_core_init_main_conf(ngx_conf_t *cf, void *conf); @@ -22,6 +27,8 @@ static char *ngx_stream_core_server(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); static char *ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); +static char *ngx_stream_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); static char *ngx_stream_core_resolver(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -42,6 +49,20 @@ static ngx_command_t ngx_stream_core_commands[] = { offsetof(ngx_stream_core_main_conf_t, variables_hash_bucket_size), NULL }, + { ngx_string("server_names_hash_max_size"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_MAIN_CONF_OFFSET, + offsetof(ngx_stream_core_main_conf_t, server_names_hash_max_size), + NULL }, + + { ngx_string("server_names_hash_bucket_size"), + NGX_STREAM_MAIN_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_STREAM_MAIN_CONF_OFFSET, + offsetof(ngx_stream_core_main_conf_t, server_names_hash_bucket_size), + NULL }, + { ngx_string("server"), NGX_STREAM_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_NOARGS, ngx_stream_core_server, @@ -56,6 +77,13 @@ static ngx_command_t ngx_stream_core_commands[] = { 0, NULL }, + { ngx_string("server_name"), + NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, + ngx_stream_core_server_name, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + { ngx_string("error_log"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, ngx_stream_core_error_log, @@ -203,8 +231,6 @@ ngx_int_t ngx_stream_core_preread_phase(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph) { - size_t size; - ssize_t n; ngx_int_t rc; ngx_connection_t *c; ngx_stream_core_srv_conf_t *cscf; @@ -217,57 +243,34 @@ ngx_stream_core_preread_phase(ngx_stream_session_t *s, if (c->read->timedout) { rc = NGX_STREAM_OK; - - } else if (c->read->timer_set) { - rc = NGX_AGAIN; - - } else { - rc = ph->handler(s); + goto done; } - while (rc == NGX_AGAIN) { - - if (c->buffer == NULL) { - c->buffer = ngx_create_temp_buf(c->pool, cscf->preread_buffer_size); - if (c->buffer == NULL) { - rc = NGX_ERROR; - break; - } - } - - size = c->buffer->end - c->buffer->last; - - if (size == 0) { - ngx_log_error(NGX_LOG_ERR, c->log, 0, "preread buffer full"); - rc = NGX_STREAM_BAD_REQUEST; - break; - } - - if (c->read->eof) { - rc = NGX_STREAM_OK; - break; - } - - if (!c->read->ready) { - break; - } - - n = c->recv(c, c->buffer->last, size); + if (!c->read->timer_set) { + rc = ph->handler(s); - if (n == NGX_ERROR || n == 0) { - rc = NGX_STREAM_OK; - break; + if (rc != NGX_AGAIN) { + goto done; } + } - if (n == NGX_AGAIN) { - break; + if (c->buffer == NULL) { + c->buffer = ngx_create_temp_buf(c->pool, cscf->preread_buffer_size); + if (c->buffer == NULL) { + rc = NGX_ERROR; + goto done; } + } - c->buffer->last += n; + if (ngx_stream_preread_can_peek(c)) { + rc = ngx_stream_preread_peek(s, ph); - rc = ph->handler(s); + } else { + rc = ngx_stream_preread(s, ph); } +done: + if (rc == NGX_AGAIN) { if (ngx_handle_read_event(c->read, 0) != NGX_OK) { ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); @@ -311,6 +314,129 @@ ngx_stream_core_preread_phase(ngx_stream_session_t *s, } +static ngx_uint_t +ngx_stream_preread_can_peek(ngx_connection_t *c) +{ +#if (NGX_STREAM_SSL) + if (c->ssl) { + return 0; + } +#endif + + if ((ngx_event_flags & NGX_USE_CLEAR_EVENT) == 0) { + return 0; + } + +#if (NGX_HAVE_KQUEUE) + if (ngx_event_flags & NGX_USE_KQUEUE_EVENT) { + return 1; + } +#endif + +#if (NGX_HAVE_EPOLLRDHUP) + if ((ngx_event_flags & NGX_USE_EPOLL_EVENT) && ngx_use_epoll_rdhup) { + return 1; + } +#endif + + return 0; +} + + +static ngx_int_t +ngx_stream_preread_peek(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph) +{ + ssize_t n; + ngx_int_t rc; + ngx_err_t err; + ngx_connection_t *c; + + c = s->connection; + + n = recv(c->fd, (char *) c->buffer->last, + c->buffer->end - c->buffer->last, MSG_PEEK); + + err = ngx_socket_errno; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream recv(): %z", n); + + if (n == -1) { + if (err == NGX_EAGAIN) { + c->read->ready = 0; + return NGX_AGAIN; + } + + ngx_connection_error(c, err, "recv() failed"); + return NGX_STREAM_OK; + } + + if (n == 0) { + return NGX_STREAM_OK; + } + + c->buffer->last += n; + + rc = ph->handler(s); + + if (rc != NGX_AGAIN) { + c->buffer->last = c->buffer->pos; + return rc; + } + + if (c->buffer->last == c->buffer->end) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "preread buffer full"); + return NGX_STREAM_BAD_REQUEST; + } + + if (c->read->pending_eof) { + return NGX_STREAM_OK; + } + + c->buffer->last = c->buffer->pos; + + return NGX_AGAIN; +} + + +static ngx_int_t +ngx_stream_preread(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph) +{ + ssize_t n; + ngx_int_t rc; + ngx_connection_t *c; + + c = s->connection; + + while (c->read->ready) { + + n = c->recv(c, c->buffer->last, c->buffer->end - c->buffer->last); + + if (n == NGX_AGAIN) { + return NGX_AGAIN; + } + + if (n == NGX_ERROR || n == 0) { + return NGX_STREAM_OK; + } + + c->buffer->last += n; + + rc = ph->handler(s); + + if (rc != NGX_AGAIN) { + return rc; + } + + if (c->buffer->last == c->buffer->end) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "preread buffer full"); + return NGX_STREAM_BAD_REQUEST; + } + } + + return NGX_AGAIN; +} + + ngx_int_t ngx_stream_core_content_phase(ngx_stream_session_t *s, ngx_stream_phase_handler_t *ph) @@ -338,6 +464,149 @@ ngx_stream_core_content_phase(ngx_stream_session_t *s, } +ngx_int_t +ngx_stream_validate_host(ngx_str_t *host, ngx_pool_t *pool, ngx_uint_t alloc) +{ + u_char *h, ch; + size_t i, dot_pos, host_len; + + enum { + sw_usual = 0, + sw_literal, + sw_rest + } state; + + dot_pos = host->len; + host_len = host->len; + + h = host->data; + + state = sw_usual; + + for (i = 0; i < host->len; i++) { + ch = h[i]; + + switch (ch) { + + case '.': + if (dot_pos == i - 1) { + return NGX_DECLINED; + } + dot_pos = i; + break; + + case ':': + if (state == sw_usual) { + host_len = i; + state = sw_rest; + } + break; + + case '[': + if (i == 0) { + state = sw_literal; + } + break; + + case ']': + if (state == sw_literal) { + host_len = i + 1; + state = sw_rest; + } + break; + + default: + + if (ngx_path_separator(ch)) { + return NGX_DECLINED; + } + + if (ch <= 0x20 || ch == 0x7f) { + return NGX_DECLINED; + } + + if (ch >= 'A' && ch <= 'Z') { + alloc = 1; + } + + break; + } + } + + if (dot_pos == host_len - 1) { + host_len--; + } + + if (host_len == 0) { + return NGX_DECLINED; + } + + if (alloc) { + host->data = ngx_pnalloc(pool, host_len); + if (host->data == NULL) { + return NGX_ERROR; + } + + ngx_strlow(host->data, h, host_len); + } + + host->len = host_len; + + return NGX_OK; +} + + +ngx_int_t +ngx_stream_find_virtual_server(ngx_stream_session_t *s, + ngx_str_t *host, ngx_stream_core_srv_conf_t **cscfp) +{ + ngx_stream_core_srv_conf_t *cscf; + + if (s->virtual_names == NULL) { + return NGX_DECLINED; + } + + cscf = ngx_hash_find_combined(&s->virtual_names->names, + ngx_hash_key(host->data, host->len), + host->data, host->len); + + if (cscf) { + *cscfp = cscf; + return NGX_OK; + } + +#if (NGX_PCRE) + + if (host->len && s->virtual_names->nregex) { + ngx_int_t n; + ngx_uint_t i; + ngx_stream_server_name_t *sn; + + sn = s->virtual_names->regex; + + for (i = 0; i < s->virtual_names->nregex; i++) { + + n = ngx_stream_regex_exec(s, sn[i].regex, host); + + if (n == NGX_DECLINED) { + continue; + } + + if (n == NGX_OK) { + *cscfp = sn[i].server; + return NGX_OK; + } + + return NGX_ERROR; + } + } + +#endif /* NGX_PCRE */ + + return NGX_DECLINED; +} + + static ngx_int_t ngx_stream_core_preconfiguration(ngx_conf_t *cf) { @@ -362,11 +631,8 @@ ngx_stream_core_create_main_conf(ngx_conf_t *cf) return NULL; } - if (ngx_array_init(&cmcf->listen, cf->pool, 4, sizeof(ngx_stream_listen_t)) - != NGX_OK) - { - return NULL; - } + cmcf->server_names_hash_max_size = NGX_CONF_UNSET_UINT; + cmcf->server_names_hash_bucket_size = NGX_CONF_UNSET_UINT; cmcf->variables_hash_max_size = NGX_CONF_UNSET_UINT; cmcf->variables_hash_bucket_size = NGX_CONF_UNSET_UINT; @@ -380,6 +646,14 @@ ngx_stream_core_init_main_conf(ngx_conf_t *cf, void *conf) { ngx_stream_core_main_conf_t *cmcf = conf; + ngx_conf_init_uint_value(cmcf->server_names_hash_max_size, 512); + ngx_conf_init_uint_value(cmcf->server_names_hash_bucket_size, + ngx_cacheline_size); + + cmcf->server_names_hash_bucket_size = + ngx_align(cmcf->server_names_hash_bucket_size, ngx_cacheline_size); + + ngx_conf_init_uint_value(cmcf->variables_hash_max_size, 1024); ngx_conf_init_uint_value(cmcf->variables_hash_bucket_size, 64); @@ -411,6 +685,13 @@ ngx_stream_core_create_srv_conf(ngx_conf_t *cf) * cscf->error_log = NULL; */ + if (ngx_array_init(&cscf->server_names, cf->temp_pool, 4, + sizeof(ngx_stream_server_name_t)) + != NGX_OK) + { + return NULL; + } + cscf->file_name = cf->conf_file->file.name.data; cscf->line = cf->conf_file->line; cscf->resolver_timeout = NGX_CONF_UNSET_MSEC; @@ -429,6 +710,9 @@ ngx_stream_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_stream_core_srv_conf_t *prev = parent; ngx_stream_core_srv_conf_t *conf = child; + ngx_str_t name; + ngx_stream_server_name_t *sn; + ngx_conf_merge_msec_value(conf->resolver_timeout, prev->resolver_timeout, 30000); @@ -476,6 +760,37 @@ ngx_stream_core_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_msec_value(conf->preread_timeout, prev->preread_timeout, 30000); + if (conf->server_names.nelts == 0) { + /* the array has 4 empty preallocated elements, so push cannot fail */ + sn = ngx_array_push(&conf->server_names); +#if (NGX_PCRE) + sn->regex = NULL; +#endif + sn->server = conf; + ngx_str_set(&sn->name, ""); + } + + sn = conf->server_names.elts; + name = sn[0].name; + +#if (NGX_PCRE) + if (sn->regex) { + name.len++; + name.data--; + } else +#endif + + if (name.data[0] == '.') { + name.len--; + name.data++; + } + + conf->server_name.len = name.len; + conf->server_name.data = ngx_pstrdup(cf->pool, &name); + if (conf->server_name.data == NULL) { + return NGX_CONF_ERROR; + } + return NGX_CONF_OK; } @@ -575,11 +890,10 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { ngx_stream_core_srv_conf_t *cscf = conf; - ngx_str_t *value, size; - ngx_url_t u; - ngx_uint_t i, n, backlog; - ngx_stream_listen_t *ls, *als; - ngx_stream_core_main_conf_t *cmcf; + ngx_str_t *value, size; + ngx_url_t u; + ngx_uint_t n, i, backlog; + ngx_stream_listen_opt_t lsopt; cscf->listen = 1; @@ -600,51 +914,67 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); - - ls = ngx_array_push_n(&cmcf->listen, u.naddrs); - if (ls == NULL) { - return NGX_CONF_ERROR; - } - - ngx_memzero(ls, sizeof(ngx_stream_listen_t)); - - ls->backlog = NGX_LISTEN_BACKLOG; - ls->rcvbuf = -1; - ls->sndbuf = -1; - ls->type = SOCK_STREAM; - ls->ctx = cf->ctx; + ngx_memzero(&lsopt, sizeof(ngx_stream_listen_opt_t)); + lsopt.backlog = NGX_LISTEN_BACKLOG; + lsopt.type = SOCK_STREAM; + lsopt.rcvbuf = -1; + lsopt.sndbuf = -1; +#if (NGX_HAVE_SETFIB) + lsopt.setfib = -1; +#endif #if (NGX_HAVE_TCP_FASTOPEN) - ls->fastopen = -1; + lsopt.fastopen = -1; #endif - #if (NGX_HAVE_INET6) - ls->ipv6only = 1; + lsopt.ipv6only = 1; #endif backlog = 0; for (i = 2; i < cf->args->nelts; i++) { + if (ngx_strcmp(value[i].data, "default_server") == 0) { + lsopt.default_server = 1; + continue; + } + #if !(NGX_WIN32) if (ngx_strcmp(value[i].data, "udp") == 0) { - ls->type = SOCK_DGRAM; + lsopt.type = SOCK_DGRAM; continue; } #endif if (ngx_strcmp(value[i].data, "bind") == 0) { - ls->bind = 1; + lsopt.set = 1; + lsopt.bind = 1; + continue; + } + +#if (NGX_HAVE_SETFIB) + if (ngx_strncmp(value[i].data, "setfib=", 7) == 0) { + lsopt.setfib = ngx_atoi(value[i].data + 7, value[i].len - 7); + lsopt.set = 1; + lsopt.bind = 1; + + if (lsopt.setfib == NGX_ERROR) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "invalid setfib \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + continue; } +#endif #if (NGX_HAVE_TCP_FASTOPEN) if (ngx_strncmp(value[i].data, "fastopen=", 9) == 0) { - ls->fastopen = ngx_atoi(value[i].data + 9, value[i].len - 9); - ls->bind = 1; + lsopt.fastopen = ngx_atoi(value[i].data + 9, value[i].len - 9); + lsopt.set = 1; + lsopt.bind = 1; - if (ls->fastopen == NGX_ERROR) { + if (lsopt.fastopen == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid fastopen \"%V\"", &value[i]); return NGX_CONF_ERROR; @@ -655,10 +985,11 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif if (ngx_strncmp(value[i].data, "backlog=", 8) == 0) { - ls->backlog = ngx_atoi(value[i].data + 8, value[i].len - 8); - ls->bind = 1; + lsopt.backlog = ngx_atoi(value[i].data + 8, value[i].len - 8); + lsopt.set = 1; + lsopt.bind = 1; - if (ls->backlog == NGX_ERROR || ls->backlog == 0) { + if (lsopt.backlog == NGX_ERROR || lsopt.backlog == 0) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid backlog \"%V\"", &value[i]); return NGX_CONF_ERROR; @@ -673,10 +1004,11 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) size.len = value[i].len - 7; size.data = value[i].data + 7; - ls->rcvbuf = ngx_parse_size(&size); - ls->bind = 1; + lsopt.rcvbuf = ngx_parse_size(&size); + lsopt.set = 1; + lsopt.bind = 1; - if (ls->rcvbuf == NGX_ERROR) { + if (lsopt.rcvbuf == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid rcvbuf \"%V\"", &value[i]); return NGX_CONF_ERROR; @@ -689,10 +1021,11 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) size.len = value[i].len - 7; size.data = value[i].data + 7; - ls->sndbuf = ngx_parse_size(&size); - ls->bind = 1; + lsopt.sndbuf = ngx_parse_size(&size); + lsopt.set = 1; + lsopt.bind = 1; - if (ls->sndbuf == NGX_ERROR) { + if (lsopt.sndbuf == NGX_ERROR) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid sndbuf \"%V\"", &value[i]); return NGX_CONF_ERROR; @@ -701,13 +1034,40 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) continue; } + if (ngx_strncmp(value[i].data, "accept_filter=", 14) == 0) { +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + lsopt.accept_filter = (char *) &value[i].data[14]; + lsopt.set = 1; + lsopt.bind = 1; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "accept filters \"%V\" are not supported " + "on this platform, ignored", + &value[i]); +#endif + continue; + } + + if (ngx_strcmp(value[i].data, "deferred") == 0) { +#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) + lsopt.deferred_accept = 1; + lsopt.set = 1; + lsopt.bind = 1; +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "the deferred accept is not supported " + "on this platform, ignored"); +#endif + continue; + } + if (ngx_strncmp(value[i].data, "ipv6only=o", 10) == 0) { #if (NGX_HAVE_INET6 && defined IPV6_V6ONLY) if (ngx_strcmp(&value[i].data[10], "n") == 0) { - ls->ipv6only = 1; + lsopt.ipv6only = 1; } else if (ngx_strcmp(&value[i].data[10], "ff") == 0) { - ls->ipv6only = 0; + lsopt.ipv6only = 0; } else { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, @@ -716,11 +1076,13 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - ls->bind = 1; + lsopt.set = 1; + lsopt.bind = 1; + continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "bind ipv6only is not supported " + "ipv6only is not supported " "on this platform"); return NGX_CONF_ERROR; #endif @@ -728,8 +1090,9 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (ngx_strcmp(value[i].data, "reuseport") == 0) { #if (NGX_HAVE_REUSEPORT) - ls->reuseport = 1; - ls->bind = 1; + lsopt.reuseport = 1; + lsopt.set = 1; + lsopt.bind = 1; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "reuseport is not supported " @@ -740,17 +1103,7 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (ngx_strcmp(value[i].data, "ssl") == 0) { #if (NGX_STREAM_SSL) - ngx_stream_ssl_conf_t *sslcf; - - sslcf = ngx_stream_conf_get_module_srv_conf(cf, - ngx_stream_ssl_module); - - sslcf->listen = 1; - sslcf->file = cf->conf_file->file.name.data; - sslcf->line = cf->conf_file->line; - - ls->ssl = 1; - + lsopt.ssl = 1; continue; #else ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, @@ -763,10 +1116,10 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (ngx_strncmp(value[i].data, "so_keepalive=", 13) == 0) { if (ngx_strcmp(&value[i].data[13], "on") == 0) { - ls->so_keepalive = 1; + lsopt.so_keepalive = 1; } else if (ngx_strcmp(&value[i].data[13], "off") == 0) { - ls->so_keepalive = 2; + lsopt.so_keepalive = 2; } else { @@ -785,8 +1138,8 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (p > s.data) { s.len = p - s.data; - ls->tcp_keepidle = ngx_parse_time(&s, 1); - if (ls->tcp_keepidle == (time_t) NGX_ERROR) { + lsopt.tcp_keepidle = ngx_parse_time(&s, 1); + if (lsopt.tcp_keepidle == (time_t) NGX_ERROR) { goto invalid_so_keepalive; } } @@ -801,8 +1154,8 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (p > s.data) { s.len = p - s.data; - ls->tcp_keepintvl = ngx_parse_time(&s, 1); - if (ls->tcp_keepintvl == (time_t) NGX_ERROR) { + lsopt.tcp_keepintvl = ngx_parse_time(&s, 1); + if (lsopt.tcp_keepintvl == (time_t) NGX_ERROR) { goto invalid_so_keepalive; } } @@ -812,19 +1165,19 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) if (s.data < end) { s.len = end - s.data; - ls->tcp_keepcnt = ngx_atoi(s.data, s.len); - if (ls->tcp_keepcnt == NGX_ERROR) { + lsopt.tcp_keepcnt = ngx_atoi(s.data, s.len); + if (lsopt.tcp_keepcnt == NGX_ERROR) { goto invalid_so_keepalive; } } - if (ls->tcp_keepidle == 0 && ls->tcp_keepintvl == 0 - && ls->tcp_keepcnt == 0) + if (lsopt.tcp_keepidle == 0 && lsopt.tcp_keepintvl == 0 + && lsopt.tcp_keepcnt == 0) { goto invalid_so_keepalive; } - ls->so_keepalive = 1; + lsopt.so_keepalive = 1; #else @@ -836,7 +1189,8 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) #endif } - ls->bind = 1; + lsopt.set = 1; + lsopt.bind = 1; continue; @@ -851,68 +1205,176 @@ ngx_stream_core_listen(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) } if (ngx_strcmp(value[i].data, "proxy_protocol") == 0) { - ls->proxy_protocol = 1; + lsopt.proxy_protocol = 1; continue; } ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "the invalid \"%V\" parameter", &value[i]); + "invalid parameter \"%V\"", &value[i]); return NGX_CONF_ERROR; } - if (ls->type == SOCK_DGRAM) { + if (lsopt.type == SOCK_DGRAM) { +#if (NGX_HAVE_TCP_FASTOPEN) + if (lsopt.fastopen != -1) { + return "\"fastopen\" parameter is incompatible with \"udp\""; + } +#endif + if (backlog) { return "\"backlog\" parameter is incompatible with \"udp\""; } +#if (NGX_HAVE_DEFERRED_ACCEPT && defined SO_ACCEPTFILTER) + if (lsopt.accept_filter) { + return "\"accept_filter\" parameter is incompatible with \"udp\""; + } +#endif + +#if (NGX_HAVE_DEFERRED_ACCEPT && defined TCP_DEFER_ACCEPT) + if (lsopt.deferred_accept) { + return "\"deferred\" parameter is incompatible with \"udp\""; + } +#endif + #if (NGX_STREAM_SSL) - if (ls->ssl) { + if (lsopt.ssl) { return "\"ssl\" parameter is incompatible with \"udp\""; } #endif - if (ls->so_keepalive) { + if (lsopt.so_keepalive) { return "\"so_keepalive\" parameter is incompatible with \"udp\""; } - if (ls->proxy_protocol) { + if (lsopt.proxy_protocol) { return "\"proxy_protocol\" parameter is incompatible with \"udp\""; } + } -#if (NGX_HAVE_TCP_FASTOPEN) - if (ls->fastopen != -1) { - return "\"fastopen\" parameter is incompatible with \"udp\""; + for (n = 0; n < u.naddrs; n++) { + + for (i = 0; i < n; i++) { + if (ngx_cmp_sockaddr(u.addrs[n].sockaddr, u.addrs[n].socklen, + u.addrs[i].sockaddr, u.addrs[i].socklen, 1) + == NGX_OK) + { + goto next; + } } -#endif + + lsopt.sockaddr = u.addrs[n].sockaddr; + lsopt.socklen = u.addrs[n].socklen; + lsopt.addr_text = u.addrs[n].name; + lsopt.wildcard = ngx_inet_wildcard(lsopt.sockaddr); + + if (ngx_stream_add_listen(cf, cscf, &lsopt) != NGX_OK) { + return NGX_CONF_ERROR; + } + + next: + continue; } - als = cmcf->listen.elts; + return NGX_CONF_OK; +} - for (n = 0; n < u.naddrs; n++) { - ls[n] = ls[0]; - ls[n].sockaddr = u.addrs[n].sockaddr; - ls[n].socklen = u.addrs[n].socklen; - ls[n].addr_text = u.addrs[n].name; - ls[n].wildcard = ngx_inet_wildcard(ls[n].sockaddr); +static char * +ngx_stream_core_server_name(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_core_srv_conf_t *cscf = conf; - for (i = 0; i < cmcf->listen.nelts - u.naddrs + n; i++) { - if (ls[n].type != als[i].type) { - continue; - } + u_char ch; + ngx_str_t *value; + ngx_uint_t i; + ngx_stream_server_name_t *sn; - if (ngx_cmp_sockaddr(als[i].sockaddr, als[i].socklen, - ls[n].sockaddr, ls[n].socklen, 1) - != NGX_OK) - { - continue; - } + value = cf->args->elts; + + for (i = 1; i < cf->args->nelts; i++) { + + ch = value[i].data[0]; + + if ((ch == '*' && (value[i].len < 3 || value[i].data[1] != '.')) + || (ch == '.' && value[i].len < 2)) + { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "server name \"%V\" is invalid", &value[i]); + return NGX_CONF_ERROR; + } + + if (ngx_strchr(value[i].data, '/')) { + ngx_conf_log_error(NGX_LOG_WARN, cf, 0, + "server name \"%V\" has suspicious symbols", + &value[i]); + } + + sn = ngx_array_push(&cscf->server_names); + if (sn == NULL) { + return NGX_CONF_ERROR; + } + +#if (NGX_PCRE) + sn->regex = NULL; +#endif + sn->server = cscf; + + if (ngx_strcasecmp(value[i].data, (u_char *) "$hostname") == 0) { + sn->name = cf->cycle->hostname; + + } else { + sn->name = value[i]; + } + + if (value[i].data[0] != '~') { + ngx_strlow(sn->name.data, sn->name.data, sn->name.len); + continue; + } + +#if (NGX_PCRE) + { + u_char *p; + ngx_regex_compile_t rc; + u_char errstr[NGX_MAX_CONF_ERRSTR]; + if (value[i].len == 1) { ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, - "duplicate \"%V\" address and port pair", - &ls[n].addr_text); + "empty regex in server name \"%V\"", &value[i]); + return NGX_CONF_ERROR; + } + + value[i].len--; + value[i].data++; + + ngx_memzero(&rc, sizeof(ngx_regex_compile_t)); + + rc.pattern = value[i]; + rc.err.len = NGX_MAX_CONF_ERRSTR; + rc.err.data = errstr; + + for (p = value[i].data; p < value[i].data + value[i].len; p++) { + if (*p >= 'A' && *p <= 'Z') { + rc.options = NGX_REGEX_CASELESS; + break; + } + } + + sn->regex = ngx_stream_regex_compile(cf, &rc); + if (sn->regex == NULL) { return NGX_CONF_ERROR; } + + sn->name = value[i]; + cscf->captures = (rc.captures > 0); + } +#else + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "using regex \"%V\" " + "requires PCRE library", &value[i]); + + return NGX_CONF_ERROR; +#endif } return NGX_CONF_OK; diff --git a/src/stream/ngx_stream_geo_module.c b/src/stream/ngx_stream_geo_module.c index 4b4cad8..2324bef 100644 --- a/src/stream/ngx_stream_geo_module.c +++ b/src/stream/ngx_stream_geo_module.c @@ -190,7 +190,7 @@ ngx_stream_geo_cidr_variable(ngx_stream_session_t *s, p = inaddr6->s6_addr; if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; @@ -263,7 +263,7 @@ ngx_stream_geo_range_variable(ngx_stream_session_t *s, if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { p = inaddr6->s6_addr; - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; @@ -1209,7 +1209,7 @@ ngx_stream_geo_value(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, return gvvn->value; } - val = ngx_palloc(ctx->pool, sizeof(ngx_stream_variable_value_t)); + val = ngx_pcalloc(ctx->pool, sizeof(ngx_stream_variable_value_t)); if (val == NULL) { return NULL; } @@ -1221,8 +1221,6 @@ ngx_stream_geo_value(ngx_conf_t *cf, ngx_stream_geo_conf_ctx_t *ctx, } val->valid = 1; - val->no_cacheable = 0; - val->not_found = 0; gvvn = ngx_palloc(ctx->temp_pool, sizeof(ngx_stream_geo_variable_value_node_t)); diff --git a/src/stream/ngx_stream_geoip_module.c b/src/stream/ngx_stream_geoip_module.c index 6507b71..3ee8f0e 100644 --- a/src/stream/ngx_stream_geoip_module.c +++ b/src/stream/ngx_stream_geoip_module.c @@ -236,7 +236,7 @@ ngx_stream_geoip_addr(ngx_stream_session_t *s, ngx_stream_geoip_conf_t *gcf) if (IN6_IS_ADDR_V4MAPPED(inaddr6)) { p = inaddr6->s6_addr; - inaddr = p[12] << 24; + inaddr = (in_addr_t) p[12] << 24; inaddr += p[13] << 16; inaddr += p[14] << 8; inaddr += p[15]; diff --git a/src/stream/ngx_stream_handler.c b/src/stream/ngx_stream_handler.c index 669b6a1..a7ffc6e 100644 --- a/src/stream/ngx_stream_handler.c +++ b/src/stream/ngx_stream_handler.c @@ -30,6 +30,7 @@ ngx_stream_init_connection(ngx_connection_t *c) struct sockaddr_in *sin; ngx_stream_in_addr_t *addr; ngx_stream_session_t *s; + ngx_stream_conf_ctx_t *ctx; ngx_stream_addr_conf_t *addr_conf; #if (NGX_HAVE_INET6) struct sockaddr_in6 *sin6; @@ -121,9 +122,12 @@ ngx_stream_init_connection(ngx_connection_t *c) return; } + ctx = addr_conf->default_server->ctx; + s->signature = NGX_STREAM_MODULE; - s->main_conf = addr_conf->ctx->main_conf; - s->srv_conf = addr_conf->ctx->srv_conf; + s->main_conf = ctx->main_conf; + s->srv_conf = ctx->srv_conf; + s->virtual_names = addr_conf->virtual_names; #if (NGX_STREAM_SSL) s->ssl = addr_conf->ssl; @@ -144,7 +148,7 @@ ngx_stream_init_connection(ngx_connection_t *c) ngx_log_error(NGX_LOG_INFO, c->log, 0, "*%uA %sclient %*s connected to %V", c->number, c->type == SOCK_DGRAM ? "udp " : "", - len, text, &addr_conf->addr_text); + len, text, &c->listening->addr_text); c->log->connection = c->number; c->log->handler = ngx_stream_log_error; diff --git a/src/stream/ngx_stream_pass_module.c b/src/stream/ngx_stream_pass_module.c new file mode 100644 index 0000000..2c1c60c --- /dev/null +++ b/src/stream/ngx_stream_pass_module.c @@ -0,0 +1,327 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include <ngx_config.h> +#include <ngx_core.h> +#include <ngx_stream.h> + + +#define NGX_STREAM_PASS_MAX_PASSES 10 + + +typedef struct { + ngx_addr_t *addr; + ngx_stream_complex_value_t *addr_value; +} ngx_stream_pass_srv_conf_t; + + +static void ngx_stream_pass_handler(ngx_stream_session_t *s); +static ngx_int_t ngx_stream_pass_check_cycle(ngx_connection_t *c); +static void ngx_stream_pass_cleanup(void *data); +static ngx_int_t ngx_stream_pass_match(ngx_listening_t *ls, ngx_addr_t *addr); +static void *ngx_stream_pass_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); + + +static ngx_command_t ngx_stream_pass_commands[] = { + + { ngx_string("pass"), + NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, + ngx_stream_pass, + NGX_STREAM_SRV_CONF_OFFSET, + 0, + NULL }, + + ngx_null_command +}; + + +static ngx_stream_module_t ngx_stream_pass_module_ctx = { + NULL, /* preconfiguration */ + NULL, /* postconfiguration */ + + NULL, /* create main configuration */ + NULL, /* init main configuration */ + + ngx_stream_pass_create_srv_conf, /* create server configuration */ + NULL /* merge server configuration */ +}; + + +ngx_module_t ngx_stream_pass_module = { + NGX_MODULE_V1, + &ngx_stream_pass_module_ctx, /* module context */ + ngx_stream_pass_commands, /* module directives */ + NGX_STREAM_MODULE, /* module type */ + NULL, /* init master */ + NULL, /* init module */ + NULL, /* init process */ + NULL, /* init thread */ + NULL, /* exit thread */ + NULL, /* exit process */ + NULL, /* exit master */ + NGX_MODULE_V1_PADDING +}; + + +static void +ngx_stream_pass_handler(ngx_stream_session_t *s) +{ + ngx_url_t u; + ngx_str_t url; + ngx_addr_t *addr; + ngx_uint_t i; + ngx_listening_t *ls; + ngx_connection_t *c; + ngx_stream_pass_srv_conf_t *pscf; + + c = s->connection; + + c->log->action = "passing connection to port"; + + if (c->buffer && c->buffer->pos != c->buffer->last) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "cannot pass connection with preread data"); + goto failed; + } + + pscf = ngx_stream_get_module_srv_conf(s, ngx_stream_pass_module); + + addr = pscf->addr; + + if (addr == NULL) { + if (ngx_stream_complex_value(s, pscf->addr_value, &url) != NGX_OK) { + goto failed; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = url; + u.no_resolve = 1; + + if (ngx_parse_url(c->pool, &u) != NGX_OK) { + if (u.err) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "%s in pass \"%V\"", u.err, &u.url); + } + + goto failed; + } + + if (u.naddrs == 0) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no addresses in pass \"%V\"", &u.url); + goto failed; + } + + if (u.no_port) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "no port in pass \"%V\"", &u.url); + goto failed; + } + + addr = &u.addrs[0]; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "stream pass addr: \"%V\"", &addr->name); + + if (ngx_stream_pass_check_cycle(c) != NGX_OK) { + goto failed; + } + + ls = ngx_cycle->listening.elts; + + for (i = 0; i < ngx_cycle->listening.nelts; i++) { + + if (ngx_stream_pass_match(&ls[i], addr) != NGX_OK) { + continue; + } + + c->listening = &ls[i]; + + c->data = NULL; + c->buffer = NULL; + + *c->log = c->listening->log; + c->log->handler = NULL; + c->log->data = NULL; + + c->local_sockaddr = addr->sockaddr; + c->local_socklen = addr->socklen; + + c->listening->handler(c); + + return; + } + + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "port not found for \"%V\"", &addr->name); + + ngx_stream_finalize_session(s, NGX_STREAM_OK); + + return; + +failed: + + ngx_stream_finalize_session(s, NGX_STREAM_INTERNAL_SERVER_ERROR); +} + + +static ngx_int_t +ngx_stream_pass_check_cycle(ngx_connection_t *c) +{ + ngx_uint_t *num; + ngx_pool_cleanup_t *cln; + + for (cln = c->pool->cleanup; cln; cln = cln->next) { + if (cln->handler != ngx_stream_pass_cleanup) { + continue; + } + + num = cln->data; + + if (++(*num) > NGX_STREAM_PASS_MAX_PASSES) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, "stream pass cycle"); + return NGX_ERROR; + } + + return NGX_OK; + } + + cln = ngx_pool_cleanup_add(c->pool, sizeof(ngx_uint_t)); + if (cln == NULL) { + return NGX_ERROR; + } + + cln->handler = ngx_stream_pass_cleanup; + + num = cln->data; + *num = 1; + + return NGX_OK; +} + + +static void +ngx_stream_pass_cleanup(void *data) +{ + return; +} + + +static ngx_int_t +ngx_stream_pass_match(ngx_listening_t *ls, ngx_addr_t *addr) +{ + if (!ls->wildcard) { + return ngx_cmp_sockaddr(ls->sockaddr, ls->socklen, + addr->sockaddr, addr->socklen, 1); + } + + if (ls->sockaddr->sa_family == addr->sockaddr->sa_family + && ngx_inet_get_port(ls->sockaddr) == ngx_inet_get_port(addr->sockaddr)) + { + return NGX_OK; + } + + return NGX_DECLINED; +} + + +static void * +ngx_stream_pass_create_srv_conf(ngx_conf_t *cf) +{ + ngx_stream_pass_srv_conf_t *conf; + + conf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_pass_srv_conf_t)); + if (conf == NULL) { + return NULL; + } + + /* + * set by ngx_pcalloc(): + * + * conf->addr = NULL; + * conf->addr_value = NULL; + */ + + return conf; +} + + +static char * +ngx_stream_pass(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_stream_pass_srv_conf_t *pscf = conf; + + ngx_url_t u; + ngx_str_t *value, *url; + ngx_stream_complex_value_t cv; + ngx_stream_core_srv_conf_t *cscf; + ngx_stream_compile_complex_value_t ccv; + + if (pscf->addr || pscf->addr_value) { + return "is duplicate"; + } + + cscf = ngx_stream_conf_get_module_srv_conf(cf, ngx_stream_core_module); + + cscf->handler = ngx_stream_pass_handler; + + value = cf->args->elts; + + url = &value[1]; + + ngx_memzero(&ccv, sizeof(ngx_stream_compile_complex_value_t)); + + ccv.cf = cf; + ccv.value = url; + ccv.complex_value = &cv; + + if (ngx_stream_compile_complex_value(&ccv) != NGX_OK) { + return NGX_CONF_ERROR; + } + + if (cv.lengths) { + pscf->addr_value = ngx_palloc(cf->pool, + sizeof(ngx_stream_complex_value_t)); + if (pscf->addr_value == NULL) { + return NGX_CONF_ERROR; + } + + *pscf->addr_value = cv; + + return NGX_CONF_OK; + } + + ngx_memzero(&u, sizeof(ngx_url_t)); + + u.url = *url; + u.no_resolve = 1; + + if (ngx_parse_url(cf->pool, &u) != NGX_OK) { + if (u.err) { + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "%s in \"%V\" of the \"pass\" directive", + u.err, &u.url); + } + + return NGX_CONF_ERROR; + } + + if (u.naddrs == 0) { + return "has no addresses"; + } + + if (u.no_port) { + return "has no port"; + } + + pscf->addr = &u.addrs[0]; + + return NGX_CONF_OK; +} diff --git a/src/stream/ngx_stream_proxy_module.c b/src/stream/ngx_stream_proxy_module.c index 934e7d8..ed275c0 100644 --- a/src/stream/ngx_stream_proxy_module.c +++ b/src/stream/ngx_stream_proxy_module.c @@ -103,6 +103,8 @@ static void ngx_stream_proxy_ssl_handshake(ngx_connection_t *pc); static void ngx_stream_proxy_ssl_save_session(ngx_connection_t *c); static ngx_int_t ngx_stream_proxy_ssl_name(ngx_stream_session_t *s); static ngx_int_t ngx_stream_proxy_ssl_certificate(ngx_stream_session_t *s); +static ngx_int_t ngx_stream_proxy_merge_ssl(ngx_conf_t *cf, + ngx_stream_proxy_srv_conf_t *conf, ngx_stream_proxy_srv_conf_t *prev); static ngx_int_t ngx_stream_proxy_set_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *pscf); @@ -801,7 +803,7 @@ ngx_stream_proxy_init_upstream(ngx_stream_session_t *s) #if (NGX_STREAM_SSL) - if (pc->type == SOCK_STREAM && pscf->ssl) { + if (pc->type == SOCK_STREAM && pscf->ssl_enable) { if (u->proxy_protocol) { if (ngx_stream_proxy_send_proxy_protocol(s) != NGX_OK) { @@ -892,7 +894,7 @@ ngx_stream_proxy_init_upstream(ngx_stream_session_t *s) return; } - p = ngx_pnalloc(c->pool, NGX_PROXY_PROTOCOL_MAX_HEADER); + p = ngx_pnalloc(c->pool, NGX_PROXY_PROTOCOL_V1_MAX_HEADER); if (p == NULL) { ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); return; @@ -900,7 +902,8 @@ ngx_stream_proxy_init_upstream(ngx_stream_session_t *s) cl->buf->pos = p; - p = ngx_proxy_protocol_write(c, p, p + NGX_PROXY_PROTOCOL_MAX_HEADER); + p = ngx_proxy_protocol_write(c, p, + p + NGX_PROXY_PROTOCOL_V1_MAX_HEADER); if (p == NULL) { ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); return; @@ -944,14 +947,15 @@ ngx_stream_proxy_send_proxy_protocol(ngx_stream_session_t *s) ngx_connection_t *c, *pc; ngx_stream_upstream_t *u; ngx_stream_proxy_srv_conf_t *pscf; - u_char buf[NGX_PROXY_PROTOCOL_MAX_HEADER]; + u_char buf[NGX_PROXY_PROTOCOL_V1_MAX_HEADER]; c = s->connection; ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, "stream proxy send PROXY protocol header"); - p = ngx_proxy_protocol_write(c, buf, buf + NGX_PROXY_PROTOCOL_MAX_HEADER); + p = ngx_proxy_protocol_write(c, buf, + buf + NGX_PROXY_PROTOCOL_V1_MAX_HEADER); if (p == NULL) { ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); return NGX_ERROR; @@ -1069,8 +1073,10 @@ ngx_stream_proxy_ssl_init_connection(ngx_stream_session_t *s) } } - if (pscf->ssl_certificate && (pscf->ssl_certificate->lengths - || pscf->ssl_certificate_key->lengths)) + if (pscf->ssl_certificate + && pscf->ssl_certificate->value.len + && (pscf->ssl_certificate->lengths + || pscf->ssl_certificate_key->lengths)) { if (ngx_stream_proxy_ssl_certificate(s) != NGX_OK) { ngx_stream_proxy_finalize(s, NGX_STREAM_INTERNAL_SERVER_ERROR); @@ -1669,9 +1675,8 @@ ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream, size = b->end - b->last; - if (size && src->read->ready && !src->read->delayed - && !src->read->error) - { + if (size && src->read->ready && !src->read->delayed) { + if (limit_rate) { limit = (off_t) limit_rate * (ngx_time() - u->start_sec + 1) - *received; @@ -1735,7 +1740,7 @@ ngx_stream_proxy_process(ngx_stream_session_t *s, ngx_uint_t from_upstream, cl->buf->temporary = (n ? 1 : 0); cl->buf->last_buf = src->read->eof; - cl->buf->flush = 1; + cl->buf->flush = !src->read->eof; (*packets)++; *received += n; @@ -2148,14 +2153,19 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) #if (NGX_STREAM_SSL) + if (ngx_stream_proxy_merge_ssl(cf, conf, prev) != NGX_OK) { + return NGX_CONF_ERROR; + } + ngx_conf_merge_value(conf->ssl_enable, prev->ssl_enable, 0); ngx_conf_merge_value(conf->ssl_session_reuse, prev->ssl_session_reuse, 1); ngx_conf_merge_bitmask_value(conf->ssl_protocols, prev->ssl_protocols, - (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 - |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); + (NGX_CONF_BITMASK_SET + |NGX_SSL_TLSv1|NGX_SSL_TLSv1_1 + |NGX_SSL_TLSv1_2|NGX_SSL_TLSv1_3)); ngx_conf_merge_str_value(conf->ssl_ciphers, prev->ssl_ciphers, "DEFAULT"); @@ -2197,16 +2207,62 @@ ngx_stream_proxy_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) #if (NGX_STREAM_SSL) static ngx_int_t -ngx_stream_proxy_set_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *pscf) +ngx_stream_proxy_merge_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *conf, + ngx_stream_proxy_srv_conf_t *prev) { - ngx_pool_cleanup_t *cln; + ngx_uint_t preserve; + + if (conf->ssl_protocols == 0 + && conf->ssl_ciphers.data == NULL + && conf->ssl_certificate == NGX_CONF_UNSET_PTR + && conf->ssl_certificate_key == NGX_CONF_UNSET_PTR + && conf->ssl_passwords == NGX_CONF_UNSET_PTR + && conf->ssl_verify == NGX_CONF_UNSET + && conf->ssl_verify_depth == NGX_CONF_UNSET_UINT + && conf->ssl_trusted_certificate.data == NULL + && conf->ssl_crl.data == NULL + && conf->ssl_session_reuse == NGX_CONF_UNSET + && conf->ssl_conf_commands == NGX_CONF_UNSET_PTR) + { + if (prev->ssl) { + conf->ssl = prev->ssl; + return NGX_OK; + } + + preserve = 1; + + } else { + preserve = 0; + } - pscf->ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); - if (pscf->ssl == NULL) { + conf->ssl = ngx_pcalloc(cf->pool, sizeof(ngx_ssl_t)); + if (conf->ssl == NULL) { return NGX_ERROR; } - pscf->ssl->log = cf->log; + conf->ssl->log = cf->log; + + /* + * special handling to preserve conf->ssl + * in the "stream" section to inherit it to all servers + */ + + if (preserve) { + prev->ssl = conf->ssl; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_stream_proxy_set_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *pscf) +{ + ngx_pool_cleanup_t *cln; + + if (pscf->ssl->ctx) { + return NGX_OK; + } if (ngx_ssl_create(pscf->ssl, pscf->ssl_protocols, NULL) != NGX_OK) { return NGX_ERROR; @@ -2225,8 +2281,9 @@ ngx_stream_proxy_set_ssl(ngx_conf_t *cf, ngx_stream_proxy_srv_conf_t *pscf) return NGX_ERROR; } - if (pscf->ssl_certificate) { - + if (pscf->ssl_certificate + && pscf->ssl_certificate->value.len) + { if (pscf->ssl_certificate_key == NULL) { ngx_log_error(NGX_LOG_EMERG, cf->log, 0, "no \"proxy_ssl_certificate_key\" is defined " diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c index c530832..6dee106 100644 --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -40,12 +40,12 @@ static ngx_int_t ngx_stream_ssl_variable(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_ssl_add_variables(ngx_conf_t *cf); -static void *ngx_stream_ssl_create_conf(ngx_conf_t *cf); -static char *ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, +static void *ngx_stream_ssl_create_srv_conf(ngx_conf_t *cf); +static char *ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child); static ngx_int_t ngx_stream_ssl_compile_certificates(ngx_conf_t *cf, - ngx_stream_ssl_conf_t *conf); + ngx_stream_ssl_srv_conf_t *conf); static char *ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); @@ -90,21 +90,21 @@ static ngx_command_t ngx_stream_ssl_commands[] = { NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_msec_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, handshake_timeout), + offsetof(ngx_stream_ssl_srv_conf_t, handshake_timeout), NULL }, { ngx_string("ssl_certificate"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, certificates), + offsetof(ngx_stream_ssl_srv_conf_t, certificates), NULL }, { ngx_string("ssl_certificate_key"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, certificate_keys), + offsetof(ngx_stream_ssl_srv_conf_t, certificate_keys), NULL }, { ngx_string("ssl_password_file"), @@ -118,63 +118,63 @@ static ngx_command_t ngx_stream_ssl_commands[] = { NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, dhparam), + offsetof(ngx_stream_ssl_srv_conf_t, dhparam), NULL }, { ngx_string("ssl_ecdh_curve"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, ecdh_curve), + offsetof(ngx_stream_ssl_srv_conf_t, ecdh_curve), NULL }, { ngx_string("ssl_protocols"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, ngx_conf_set_bitmask_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, protocols), + offsetof(ngx_stream_ssl_srv_conf_t, protocols), &ngx_stream_ssl_protocols }, { ngx_string("ssl_ciphers"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, ciphers), + offsetof(ngx_stream_ssl_srv_conf_t, ciphers), NULL }, { ngx_string("ssl_verify_client"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_enum_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, verify), + offsetof(ngx_stream_ssl_srv_conf_t, verify), &ngx_stream_ssl_verify }, { ngx_string("ssl_verify_depth"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_num_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, verify_depth), + offsetof(ngx_stream_ssl_srv_conf_t, verify_depth), NULL }, { ngx_string("ssl_client_certificate"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, client_certificate), + offsetof(ngx_stream_ssl_srv_conf_t, client_certificate), NULL }, { ngx_string("ssl_trusted_certificate"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, trusted_certificate), + offsetof(ngx_stream_ssl_srv_conf_t, trusted_certificate), NULL }, { ngx_string("ssl_prefer_server_ciphers"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, prefer_server_ciphers), + offsetof(ngx_stream_ssl_srv_conf_t, prefer_server_ciphers), NULL }, { ngx_string("ssl_session_cache"), @@ -188,37 +188,44 @@ static ngx_command_t ngx_stream_ssl_commands[] = { NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, ngx_conf_set_flag_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, session_tickets), + offsetof(ngx_stream_ssl_srv_conf_t, session_tickets), NULL }, { ngx_string("ssl_session_ticket_key"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_array_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, session_ticket_keys), + offsetof(ngx_stream_ssl_srv_conf_t, session_ticket_keys), NULL }, { ngx_string("ssl_session_timeout"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_sec_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, session_timeout), + offsetof(ngx_stream_ssl_srv_conf_t, session_timeout), NULL }, { ngx_string("ssl_crl"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE1, ngx_conf_set_str_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, crl), + offsetof(ngx_stream_ssl_srv_conf_t, crl), NULL }, { ngx_string("ssl_conf_command"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_TAKE2, ngx_conf_set_keyval_slot, NGX_STREAM_SRV_CONF_OFFSET, - offsetof(ngx_stream_ssl_conf_t, conf_commands), + offsetof(ngx_stream_ssl_srv_conf_t, conf_commands), &ngx_stream_ssl_conf_command_post }, + { ngx_string("ssl_reject_handshake"), + NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_FLAG, + ngx_conf_set_flag_slot, + NGX_STREAM_SRV_CONF_OFFSET, + offsetof(ngx_stream_ssl_srv_conf_t, reject_handshake), + NULL }, + { ngx_string("ssl_alpn"), NGX_STREAM_MAIN_CONF|NGX_STREAM_SRV_CONF|NGX_CONF_1MORE, ngx_stream_ssl_alpn, @@ -237,8 +244,8 @@ static ngx_stream_module_t ngx_stream_ssl_module_ctx = { NULL, /* create main configuration */ NULL, /* init main configuration */ - ngx_stream_ssl_create_conf, /* create server configuration */ - ngx_stream_ssl_merge_conf /* merge server configuration */ + ngx_stream_ssl_create_srv_conf, /* create server configuration */ + ngx_stream_ssl_merge_srv_conf /* merge server configuration */ }; @@ -332,11 +339,11 @@ static ngx_str_t ngx_stream_ssl_sess_id_ctx = ngx_string("STREAM"); static ngx_int_t ngx_stream_ssl_handler(ngx_stream_session_t *s) { - long rc; - X509 *cert; - ngx_int_t rv; - ngx_connection_t *c; - ngx_stream_ssl_conf_t *sslcf; + long rc; + X509 *cert; + ngx_int_t rv; + ngx_connection_t *c; + ngx_stream_ssl_srv_conf_t *sscf; if (!s->ssl) { return NGX_OK; @@ -344,23 +351,23 @@ ngx_stream_ssl_handler(ngx_stream_session_t *s) c = s->connection; - sslcf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); + sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); if (c->ssl == NULL) { c->log->action = "SSL handshaking"; - rv = ngx_stream_ssl_init_connection(&sslcf->ssl, c); + rv = ngx_stream_ssl_init_connection(&sscf->ssl, c); if (rv != NGX_OK) { return rv; } } - if (sslcf->verify) { + if (sscf->verify) { rc = SSL_get_verify_result(c->ssl->connection); if (rc != X509_V_OK - && (sslcf->verify != 3 || !ngx_ssl_verify_error_optional(rc))) + && (sscf->verify != 3 || !ngx_ssl_verify_error_optional(rc))) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client SSL certificate verify error: (%l:%s)", @@ -371,7 +378,7 @@ ngx_stream_ssl_handler(ngx_stream_session_t *s) return NGX_ERROR; } - if (sslcf->verify == 1) { + if (sscf->verify == 1) { cert = SSL_get_peer_certificate(c->ssl->connection); if (cert == NULL) { @@ -396,7 +403,7 @@ ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c) { ngx_int_t rc; ngx_stream_session_t *s; - ngx_stream_ssl_conf_t *sslcf; + ngx_stream_ssl_srv_conf_t *sscf; ngx_stream_core_srv_conf_t *cscf; s = c->data; @@ -418,9 +425,9 @@ ngx_stream_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c) } if (rc == NGX_AGAIN) { - sslcf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); + sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); - ngx_add_timer(c->read, sslcf->handshake_timeout); + ngx_add_timer(c->read, sscf->handshake_timeout); c->ssl->handler = ngx_stream_ssl_handshake_handler; @@ -458,7 +465,135 @@ ngx_stream_ssl_handshake_handler(ngx_connection_t *c) static int ngx_stream_ssl_servername(ngx_ssl_conn_t *ssl_conn, int *ad, void *arg) { + ngx_int_t rc; + ngx_str_t host; + const char *servername; + ngx_connection_t *c; + ngx_stream_session_t *s; + ngx_stream_ssl_srv_conf_t *sscf; + ngx_stream_core_srv_conf_t *cscf; + + c = ngx_ssl_get_connection(ssl_conn); + + if (c->ssl->handshaked) { + *ad = SSL_AD_NO_RENEGOTIATION; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + + s = c->data; + + servername = SSL_get_servername(ssl_conn, TLSEXT_NAMETYPE_host_name); + + if (servername == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL server name: null"); + goto done; + } + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL server name: \"%s\"", servername); + + host.len = ngx_strlen(servername); + + if (host.len == 0) { + goto done; + } + + host.data = (u_char *) servername; + + rc = ngx_stream_validate_host(&host, c->pool, 1); + + if (rc == NGX_ERROR) { + goto error; + } + + if (rc == NGX_DECLINED) { + goto done; + } + + rc = ngx_stream_find_virtual_server(s, &host, &cscf); + + if (rc == NGX_ERROR) { + goto error; + } + + if (rc == NGX_DECLINED) { + goto done; + } + + sscf = ngx_stream_get_module_srv_conf(cscf->ctx, ngx_stream_ssl_module); + +#if (defined TLS1_3_VERSION \ + && !defined LIBRESSL_VERSION_NUMBER && !defined OPENSSL_IS_BORINGSSL) + + /* + * SSL_SESSION_get0_hostname() is only available in OpenSSL 1.1.1+, + * but servername being negotiated in every TLSv1.3 handshake + * is only returned in OpenSSL 1.1.1+ as well + */ + + if (sscf->verify) { + const char *hostname; + + hostname = SSL_SESSION_get0_hostname(SSL_get0_session(ssl_conn)); + + if (hostname != NULL && ngx_strcmp(hostname, servername) != 0) { + c->ssl->handshake_rejected = 1; + *ad = SSL_AD_ACCESS_DENIED; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + } + +#endif + + s->srv_conf = cscf->ctx->srv_conf; + + ngx_set_connection_log(c, cscf->error_log); + + if (sscf->ssl.ctx) { + if (SSL_set_SSL_CTX(ssl_conn, sscf->ssl.ctx) == NULL) { + goto error; + } + + /* + * SSL_set_SSL_CTX() only changes certs as of 1.0.0d + * adjust other things we care about + */ + + SSL_set_verify(ssl_conn, SSL_CTX_get_verify_mode(sscf->ssl.ctx), + SSL_CTX_get_verify_callback(sscf->ssl.ctx)); + + SSL_set_verify_depth(ssl_conn, SSL_CTX_get_verify_depth(sscf->ssl.ctx)); + +#if OPENSSL_VERSION_NUMBER >= 0x009080dfL + /* only in 0.9.8m+ */ + SSL_clear_options(ssl_conn, SSL_get_options(ssl_conn) & + ~SSL_CTX_get_options(sscf->ssl.ctx)); +#endif + + SSL_set_options(ssl_conn, SSL_CTX_get_options(sscf->ssl.ctx)); + +#ifdef SSL_OP_NO_RENEGOTIATION + SSL_set_options(ssl_conn, SSL_OP_NO_RENEGOTIATION); +#endif + } + +done: + + sscf = ngx_stream_get_module_srv_conf(s, ngx_stream_ssl_module); + + if (sscf->reject_handshake) { + c->ssl->handshake_rejected = 1; + *ad = SSL_AD_UNRECOGNIZED_NAME; + return SSL_TLSEXT_ERR_ALERT_FATAL; + } + return SSL_TLSEXT_ERR_OK; + +error: + + *ad = SSL_AD_INTERNAL_ERROR; + return SSL_TLSEXT_ERR_ALERT_FATAL; } #endif @@ -513,7 +648,7 @@ ngx_stream_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg) ngx_uint_t i, nelts; ngx_connection_t *c; ngx_stream_session_t *s; - ngx_stream_ssl_conf_t *sslcf; + ngx_stream_ssl_srv_conf_t *sscf; ngx_stream_complex_value_t *certs, *keys; c = ngx_ssl_get_connection(ssl_conn); @@ -524,11 +659,11 @@ ngx_stream_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg) s = c->data; - sslcf = arg; + sscf = arg; - nelts = sslcf->certificate_values->nelts; - certs = sslcf->certificate_values->elts; - keys = sslcf->certificate_key_values->elts; + nelts = sscf->certificate_values->nelts; + certs = sscf->certificate_values->elts; + keys = sscf->certificate_key_values->elts; for (i = 0; i < nelts; i++) { @@ -547,7 +682,7 @@ ngx_stream_ssl_certificate(ngx_ssl_conn_t *ssl_conn, void *arg) "ssl key: \"%s\"", key.data); if (ngx_ssl_connection_certificate(c, c->pool, &cert, &key, - sslcf->passwords) + sscf->passwords) != NGX_OK) { return 0; @@ -643,53 +778,53 @@ ngx_stream_ssl_add_variables(ngx_conf_t *cf) static void * -ngx_stream_ssl_create_conf(ngx_conf_t *cf) +ngx_stream_ssl_create_srv_conf(ngx_conf_t *cf) { - ngx_stream_ssl_conf_t *scf; + ngx_stream_ssl_srv_conf_t *sscf; - scf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_ssl_conf_t)); - if (scf == NULL) { + sscf = ngx_pcalloc(cf->pool, sizeof(ngx_stream_ssl_srv_conf_t)); + if (sscf == NULL) { return NULL; } /* * set by ngx_pcalloc(): * - * scf->listen = 0; - * scf->protocols = 0; - * scf->certificate_values = NULL; - * scf->dhparam = { 0, NULL }; - * scf->ecdh_curve = { 0, NULL }; - * scf->client_certificate = { 0, NULL }; - * scf->trusted_certificate = { 0, NULL }; - * scf->crl = { 0, NULL }; - * scf->alpn = { 0, NULL }; - * scf->ciphers = { 0, NULL }; - * scf->shm_zone = NULL; + * sscf->protocols = 0; + * sscf->certificate_values = NULL; + * sscf->dhparam = { 0, NULL }; + * sscf->ecdh_curve = { 0, NULL }; + * sscf->client_certificate = { 0, NULL }; + * sscf->trusted_certificate = { 0, NULL }; + * sscf->crl = { 0, NULL }; + * sscf->alpn = { 0, NULL }; + * sscf->ciphers = { 0, NULL }; + * sscf->shm_zone = NULL; */ - scf->handshake_timeout = NGX_CONF_UNSET_MSEC; - scf->certificates = NGX_CONF_UNSET_PTR; - scf->certificate_keys = NGX_CONF_UNSET_PTR; - scf->passwords = NGX_CONF_UNSET_PTR; - scf->conf_commands = NGX_CONF_UNSET_PTR; - scf->prefer_server_ciphers = NGX_CONF_UNSET; - scf->verify = NGX_CONF_UNSET_UINT; - scf->verify_depth = NGX_CONF_UNSET_UINT; - scf->builtin_session_cache = NGX_CONF_UNSET; - scf->session_timeout = NGX_CONF_UNSET; - scf->session_tickets = NGX_CONF_UNSET; - scf->session_ticket_keys = NGX_CONF_UNSET_PTR; - - return scf; + sscf->handshake_timeout = NGX_CONF_UNSET_MSEC; + sscf->certificates = NGX_CONF_UNSET_PTR; + sscf->certificate_keys = NGX_CONF_UNSET_PTR; + sscf->passwords = NGX_CONF_UNSET_PTR; + sscf->conf_commands = NGX_CONF_UNSET_PTR; + sscf->prefer_server_ciphers = NGX_CONF_UNSET; + sscf->reject_handshake = NGX_CONF_UNSET; + sscf->verify = NGX_CONF_UNSET_UINT; + sscf->verify_depth = NGX_CONF_UNSET_UINT; + sscf->builtin_session_cache = NGX_CONF_UNSET; + sscf->session_timeout = NGX_CONF_UNSET; + sscf->session_tickets = NGX_CONF_UNSET; + sscf->session_ticket_keys = NGX_CONF_UNSET_PTR; + + return sscf; } static char * -ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) +ngx_stream_ssl_merge_srv_conf(ngx_conf_t *cf, void *parent, void *child) { - ngx_stream_ssl_conf_t *prev = parent; - ngx_stream_ssl_conf_t *conf = child; + ngx_stream_ssl_srv_conf_t *prev = parent; + ngx_stream_ssl_srv_conf_t *conf = child; ngx_pool_cleanup_t *cln; @@ -702,9 +837,12 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) ngx_conf_merge_value(conf->prefer_server_ciphers, prev->prefer_server_ciphers, 0); + ngx_conf_merge_value(conf->reject_handshake, prev->reject_handshake, 0); + ngx_conf_merge_bitmask_value(conf->protocols, prev->protocols, - (NGX_CONF_BITMASK_SET|NGX_SSL_TLSv1 - |NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2)); + (NGX_CONF_BITMASK_SET + |NGX_SSL_TLSv1|NGX_SSL_TLSv1_1 + |NGX_SSL_TLSv1_2|NGX_SSL_TLSv1_3)); ngx_conf_merge_uint_value(conf->verify, prev->verify, 0); ngx_conf_merge_uint_value(conf->verify_depth, prev->verify_depth, 1); @@ -734,35 +872,21 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) conf->ssl.log = cf->log; - if (!conf->listen) { - return NGX_CONF_OK; - } + if (conf->certificates) { - if (conf->certificates == NULL) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate\" is defined for " - "the \"listen ... ssl\" directive in %s:%ui", - conf->file, conf->line); - return NGX_CONF_ERROR; - } - - if (conf->certificate_keys == NULL) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate_key\" is defined for " - "the \"listen ... ssl\" directive in %s:%ui", - conf->file, conf->line); - return NGX_CONF_ERROR; - } + if (conf->certificate_keys == NULL + || conf->certificate_keys->nelts < conf->certificates->nelts) + { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate_key\" is defined " + "for certificate \"%V\"", + ((ngx_str_t *) conf->certificates->elts) + + conf->certificates->nelts - 1); + return NGX_CONF_ERROR; + } - if (conf->certificate_keys->nelts < conf->certificates->nelts) { - ngx_log_error(NGX_LOG_EMERG, cf->log, 0, - "no \"ssl_certificate_key\" is defined " - "for certificate \"%V\" and " - "the \"listen ... ssl\" directive in %s:%ui", - ((ngx_str_t *) conf->certificates->elts) - + conf->certificates->nelts - 1, - conf->file, conf->line); - return NGX_CONF_ERROR; + } else if (!conf->reject_handshake) { + return NGX_CONF_OK; } if (ngx_ssl_create(&conf->ssl, conf->protocols, NULL) != NGX_OK) { @@ -817,7 +941,7 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) return NGX_CONF_ERROR; #endif - } else { + } else if (conf->certificates) { /* configure certificates */ @@ -909,13 +1033,17 @@ ngx_stream_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child) static ngx_int_t ngx_stream_ssl_compile_certificates(ngx_conf_t *cf, - ngx_stream_ssl_conf_t *conf) + ngx_stream_ssl_srv_conf_t *conf) { ngx_str_t *cert, *key; ngx_uint_t i, nelts; ngx_stream_complex_value_t *cv; ngx_stream_compile_complex_value_t ccv; + if (conf->certificates == NULL) { + return NGX_OK; + } + cert = conf->certificates->elts; key = conf->certificate_keys->elts; nelts = conf->certificates->nelts; @@ -994,19 +1122,19 @@ found: static char * ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - ngx_stream_ssl_conf_t *scf = conf; + ngx_stream_ssl_srv_conf_t *sscf = conf; ngx_str_t *value; - if (scf->passwords != NGX_CONF_UNSET_PTR) { + if (sscf->passwords != NGX_CONF_UNSET_PTR) { return "is duplicate"; } value = cf->args->elts; - scf->passwords = ngx_ssl_read_password_file(cf, &value[1]); + sscf->passwords = ngx_ssl_read_password_file(cf, &value[1]); - if (scf->passwords == NULL) { + if (sscf->passwords == NULL) { return NGX_CONF_ERROR; } @@ -1017,7 +1145,7 @@ ngx_stream_ssl_password_file(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) static char * ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { - ngx_stream_ssl_conf_t *scf = conf; + ngx_stream_ssl_srv_conf_t *sscf = conf; size_t len; ngx_str_t *value, name, size; @@ -1029,17 +1157,17 @@ ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) for (i = 1; i < cf->args->nelts; i++) { if (ngx_strcmp(value[i].data, "off") == 0) { - scf->builtin_session_cache = NGX_SSL_NO_SCACHE; + sscf->builtin_session_cache = NGX_SSL_NO_SCACHE; continue; } if (ngx_strcmp(value[i].data, "none") == 0) { - scf->builtin_session_cache = NGX_SSL_NONE_SCACHE; + sscf->builtin_session_cache = NGX_SSL_NONE_SCACHE; continue; } if (ngx_strcmp(value[i].data, "builtin") == 0) { - scf->builtin_session_cache = NGX_SSL_DFLT_BUILTIN_SCACHE; + sscf->builtin_session_cache = NGX_SSL_DFLT_BUILTIN_SCACHE; continue; } @@ -1054,7 +1182,7 @@ ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) goto invalid; } - scf->builtin_session_cache = n; + sscf->builtin_session_cache = n; continue; } @@ -1073,7 +1201,7 @@ ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) len++; } - if (len == 0) { + if (len == 0 || j == value[i].len) { goto invalid; } @@ -1097,13 +1225,13 @@ ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) return NGX_CONF_ERROR; } - scf->shm_zone = ngx_shared_memory_add(cf, &name, n, + sscf->shm_zone = ngx_shared_memory_add(cf, &name, n, &ngx_stream_ssl_module); - if (scf->shm_zone == NULL) { + if (sscf->shm_zone == NULL) { return NGX_CONF_ERROR; } - scf->shm_zone->init = ngx_ssl_session_cache_init; + sscf->shm_zone->init = ngx_ssl_session_cache_init; continue; } @@ -1111,8 +1239,8 @@ ngx_stream_ssl_session_cache(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) goto invalid; } - if (scf->shm_zone && scf->builtin_session_cache == NGX_CONF_UNSET) { - scf->builtin_session_cache = NGX_SSL_NO_BUILTIN_SCACHE; + if (sscf->shm_zone && sscf->builtin_session_cache == NGX_CONF_UNSET) { + sscf->builtin_session_cache = NGX_SSL_NO_BUILTIN_SCACHE; } return NGX_CONF_OK; @@ -1131,14 +1259,14 @@ ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) { #ifdef TLSEXT_TYPE_application_layer_protocol_negotiation - ngx_stream_ssl_conf_t *scf = conf; + ngx_stream_ssl_srv_conf_t *sscf = conf; u_char *p; size_t len; ngx_str_t *value; ngx_uint_t i; - if (scf->alpn.len) { + if (sscf->alpn.len) { return "is duplicate"; } @@ -1155,19 +1283,19 @@ ngx_stream_ssl_alpn(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) len += value[i].len + 1; } - scf->alpn.data = ngx_pnalloc(cf->pool, len); - if (scf->alpn.data == NULL) { + sscf->alpn.data = ngx_pnalloc(cf->pool, len); + if (sscf->alpn.data == NULL) { return NGX_CONF_ERROR; } - p = scf->alpn.data; + p = sscf->alpn.data; for (i = 1; i < cf->args->nelts; i++) { *p++ = value[i].len; p = ngx_cpymem(p, value[i].data, value[i].len); } - scf->alpn.len = len; + sscf->alpn.len = len; return NGX_CONF_OK; @@ -1194,8 +1322,13 @@ ngx_stream_ssl_conf_command_check(ngx_conf_t *cf, void *post, void *data) static ngx_int_t ngx_stream_ssl_init(ngx_conf_t *cf) { - ngx_stream_handler_pt *h; - ngx_stream_core_main_conf_t *cmcf; + ngx_uint_t a, p, s; + ngx_stream_handler_pt *h; + ngx_stream_conf_addr_t *addr; + ngx_stream_conf_port_t *port; + ngx_stream_ssl_srv_conf_t *sscf; + ngx_stream_core_srv_conf_t **cscfp, *cscf; + ngx_stream_core_main_conf_t *cmcf; cmcf = ngx_stream_conf_get_module_main_conf(cf, ngx_stream_core_module); @@ -1206,5 +1339,58 @@ ngx_stream_ssl_init(ngx_conf_t *cf) *h = ngx_stream_ssl_handler; + if (cmcf->ports == NULL) { + return NGX_OK; + } + + port = cmcf->ports->elts; + for (p = 0; p < cmcf->ports->nelts; p++) { + + addr = port[p].addrs.elts; + for (a = 0; a < port[p].addrs.nelts; a++) { + + if (!addr[a].opt.ssl) { + continue; + } + + cscf = addr[a].default_server; + sscf = cscf->ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; + + if (sscf->certificates) { + continue; + } + + if (!sscf->reject_handshake) { + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate\" is defined for " + "the \"listen ... ssl\" directive in %s:%ui", + cscf->file_name, cscf->line); + return NGX_ERROR; + } + + /* + * if no certificates are defined in the default server, + * check all non-default server blocks + */ + + cscfp = addr[a].servers.elts; + for (s = 0; s < addr[a].servers.nelts; s++) { + + cscf = cscfp[s]; + sscf = cscf->ctx->srv_conf[ngx_stream_ssl_module.ctx_index]; + + if (sscf->certificates || sscf->reject_handshake) { + continue; + } + + ngx_log_error(NGX_LOG_EMERG, cf->log, 0, + "no \"ssl_certificate\" is defined for " + "the \"listen ... ssl\" directive in %s:%ui", + cscf->file_name, cscf->line); + return NGX_ERROR; + } + } + } + return NGX_OK; } diff --git a/src/stream/ngx_stream_ssl_module.h b/src/stream/ngx_stream_ssl_module.h index e7c825e..6f6d9ae 100644 --- a/src/stream/ngx_stream_ssl_module.h +++ b/src/stream/ngx_stream_ssl_module.h @@ -18,10 +18,10 @@ typedef struct { ngx_msec_t handshake_timeout; ngx_flag_t prefer_server_ciphers; + ngx_flag_t reject_handshake; ngx_ssl_t ssl; - ngx_uint_t listen; ngx_uint_t protocols; ngx_uint_t verify; @@ -53,10 +53,7 @@ typedef struct { ngx_flag_t session_tickets; ngx_array_t *session_ticket_keys; - - u_char *file; - ngx_uint_t line; -} ngx_stream_ssl_conf_t; +} ngx_stream_ssl_srv_conf_t; extern ngx_module_t ngx_stream_ssl_module; diff --git a/src/stream/ngx_stream_ssl_preread_module.c b/src/stream/ngx_stream_ssl_preread_module.c index a236fc5..bc96ade 100644 --- a/src/stream/ngx_stream_ssl_preread_module.c +++ b/src/stream/ngx_stream_ssl_preread_module.c @@ -33,6 +33,8 @@ typedef struct { static ngx_int_t ngx_stream_ssl_preread_handler(ngx_stream_session_t *s); static ngx_int_t ngx_stream_ssl_preread_parse_record( ngx_stream_ssl_preread_ctx_t *ctx, u_char *pos, u_char *last); +static ngx_int_t ngx_stream_ssl_preread_servername(ngx_stream_session_t *s, + ngx_str_t *servername); static ngx_int_t ngx_stream_ssl_preread_protocol_variable( ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_ssl_preread_server_name_variable( @@ -187,6 +189,10 @@ ngx_stream_ssl_preread_handler(ngx_stream_session_t *s) return NGX_DECLINED; } + if (rc == NGX_OK) { + return ngx_stream_ssl_preread_servername(s, &ctx->host); + } + if (rc != NGX_AGAIN) { return rc; } @@ -404,9 +410,6 @@ ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, case sw_sni_host: ctx->host.len = (p[1] << 8) + p[2]; - ngx_log_debug1(NGX_LOG_DEBUG_STREAM, ctx->log, 0, - "ssl preread: SNI hostname \"%V\"", &ctx->host); - state = sw_ext; dst = NULL; size = ext; @@ -496,6 +499,54 @@ ngx_stream_ssl_preread_parse_record(ngx_stream_ssl_preread_ctx_t *ctx, } +static ngx_int_t +ngx_stream_ssl_preread_servername(ngx_stream_session_t *s, + ngx_str_t *servername) +{ + ngx_int_t rc; + ngx_str_t host; + ngx_connection_t *c; + ngx_stream_core_srv_conf_t *cscf; + + c = s->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_STREAM, c->log, 0, + "SSL preread server name: \"%V\"", servername); + + if (servername->len == 0) { + return NGX_OK; + } + + host = *servername; + + rc = ngx_stream_validate_host(&host, c->pool, 1); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + + rc = ngx_stream_find_virtual_server(s, &host, &cscf); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + return NGX_OK; + } + + s->srv_conf = cscf->ctx->srv_conf; + + ngx_set_connection_log(c, cscf->error_log); + + return NGX_OK; +} + + static ngx_int_t ngx_stream_ssl_preread_protocol_variable(ngx_stream_session_t *s, ngx_variable_value_t *v, uintptr_t data) diff --git a/src/stream/ngx_stream_variables.c b/src/stream/ngx_stream_variables.c index 8b59668..4658104 100644 --- a/src/stream/ngx_stream_variables.c +++ b/src/stream/ngx_stream_variables.c @@ -23,10 +23,14 @@ static ngx_int_t ngx_stream_variable_proxy_protocol_addr( ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_variable_proxy_protocol_port( ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_proxy_protocol_tlv( + ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_variable_server_addr(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_variable_server_port(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); +static ngx_int_t ngx_stream_variable_server_name(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_variable_bytes(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data); static ngx_int_t ngx_stream_variable_session_time(ngx_stream_session_t *s, @@ -79,12 +83,19 @@ static ngx_stream_variable_t ngx_stream_core_variables[] = { ngx_stream_variable_proxy_protocol_port, offsetof(ngx_proxy_protocol_t, dst_port), 0, 0 }, + { ngx_string("proxy_protocol_tlv_"), NULL, + ngx_stream_variable_proxy_protocol_tlv, + 0, NGX_STREAM_VAR_PREFIX, 0 }, + { ngx_string("server_addr"), NULL, ngx_stream_variable_server_addr, 0, 0, 0 }, { ngx_string("server_port"), NULL, ngx_stream_variable_server_port, 0, 0, 0 }, + { ngx_string("server_name"), NULL, ngx_stream_variable_server_name, + 0, 0, 0 }, + { ngx_string("bytes_sent"), NULL, ngx_stream_variable_bytes, 0, 0, 0 }, @@ -621,6 +632,39 @@ ngx_stream_variable_proxy_protocol_port(ngx_stream_session_t *s, } +static ngx_int_t +ngx_stream_variable_proxy_protocol_tlv(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_str_t *name = (ngx_str_t *) data; + + ngx_int_t rc; + ngx_str_t tlv, value; + + tlv.len = name->len - (sizeof("proxy_protocol_tlv_") - 1); + tlv.data = name->data + sizeof("proxy_protocol_tlv_") - 1; + + rc = ngx_proxy_protocol_get_tlv(s->connection, &tlv, &value); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_DECLINED) { + v->not_found = 1; + return NGX_OK; + } + + v->len = value.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = value.data; + + return NGX_OK; +} + + static ngx_int_t ngx_stream_variable_server_addr(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) @@ -682,6 +726,24 @@ ngx_stream_variable_server_port(ngx_stream_session_t *s, } +static ngx_int_t +ngx_stream_variable_server_name(ngx_stream_session_t *s, + ngx_stream_variable_value_t *v, uintptr_t data) +{ + ngx_stream_core_srv_conf_t *cscf; + + cscf = ngx_stream_get_module_srv_conf(s, ngx_stream_core_module); + + v->len = cscf->server_name.len; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = cscf->server_name.data; + + return NGX_OK; +} + + static ngx_int_t ngx_stream_variable_bytes(ngx_stream_session_t *s, ngx_stream_variable_value_t *v, uintptr_t data) diff --git a/src/stream/ngx_stream_write_filter_module.c b/src/stream/ngx_stream_write_filter_module.c index 156a61c..07dc7b5 100644 --- a/src/stream/ngx_stream_write_filter_module.c +++ b/src/stream/ngx_stream_write_filter_module.c @@ -235,7 +235,7 @@ ngx_stream_write_filter(ngx_stream_session_t *s, ngx_chain_t *in, if (size == 0 && !(c->buffered & NGX_LOWLEVEL_BUFFERED) && !(last && c->need_last_buf) - && !(c->type == SOCK_DGRAM && flush)) + && !(flush && c->need_flush_buf)) { if (last || flush || sync) { for (cl = *out; cl; /* void */) { -- GitLab