Example configuration: location /foo { proxy_http_version 3; proxy_pass https://http3-server.example.com:4433; }
src/http/modules/ngx_http_proxy_module.c | 2276 ++++++++++++++++- src/http/modules/ngx_http_upstream_keepalive_module.c | 47 +- src/http/ngx_http_header_filter_module.c | 50 + src/http/ngx_http_request.h | 2 + src/http/ngx_http_upstream.c | 556 ++++- src/http/ngx_http_upstream.h | 14 + src/http/v3/ngx_http_v3.h | 7 + src/http/v3/ngx_http_v3_parse.c | 36 +- src/http/v3/ngx_http_v3_request.c | 23 + src/http/v3/ngx_http_v3_uni.c | 45 +- 10 files changed, 3018 insertions(+), 38 deletions(-)
# HG changeset patch # User Vladimir Khomutov <v...@wbsrv.ru> # Date 1703160644 -10800 # Thu Dec 21 15:10:44 2023 +0300 # Node ID 6150bf13f72af4f2ecc918381a2d5a8916eaf8e5 # Parent fcbbdbc00cbf51dc54f6da114e12ba5ec0f278cc Proxy: HTTP/3 support. Example configuration: location /foo { proxy_http_version 3; proxy_pass https://http3-server.example.com:4433; } diff --git a/src/http/modules/ngx_http_proxy_module.c b/src/http/modules/ngx_http_proxy_module.c --- a/src/http/modules/ngx_http_proxy_module.c +++ b/src/http/modules/ngx_http_proxy_module.c @@ -10,6 +10,10 @@ #include <ngx_core.h> #include <ngx_http.h> +#if (NGX_HTTP_V3 && NGX_QUIC_OPENSSL_COMPAT) +#include <ngx_event_quic_openssl_compat.h> +#endif + #define NGX_HTTP_PROXY_COOKIE_SECURE 0x0001 #define NGX_HTTP_PROXY_COOKIE_SECURE_ON 0x0002 @@ -131,6 +135,7 @@ typedef struct { #if (NGX_HTTP_V3) ngx_str_t host; ngx_uint_t host_set; + ngx_flag_t enable_hq; #endif } ngx_http_proxy_loc_conf_t; @@ -146,6 +151,8 @@ typedef struct { #if (NGX_HTTP_V3) ngx_str_t host; + ngx_http_v3_parse_t *v3_parse; + size_t data_recvd; #endif unsigned head:1; @@ -253,6 +260,80 @@ static ngx_int_t ngx_http_proxy_set_ssl( #endif static void ngx_http_proxy_set_vars(ngx_url_t *u, ngx_http_proxy_vars_t *v); +#if (NGX_HTTP_V3) + +/* context for creating http/3 request */ +typedef struct { + /* calculated length of request */ + size_t n; + + /* encode method state */ + ngx_str_t method; + + /* encode path state */ + size_t loc_len; + size_t uri_len; + uintptr_t escape; + ngx_uint_t unparsed_uri; + + /* encode headers state */ + size_t max_head; + ngx_http_proxy_headers_t *headers; + ngx_http_script_engine_t le; + ngx_http_script_engine_t e; + +} ngx_http_v3_proxy_ctx_t; + + +static char *ngx_http_v3_proxy_host_key(ngx_conf_t *cf, ngx_command_t *cmd, + void *conf); +static ngx_int_t ngx_http_v3_proxy_merge_quic(ngx_conf_t *cf, + ngx_http_proxy_loc_conf_t *conf, ngx_http_proxy_loc_conf_t *prev); + +static ngx_int_t ngx_http_v3_proxy_create_request(ngx_http_request_t *r); + +static ngx_chain_t *ngx_http_v3_create_headers_frame(ngx_http_request_t *r, + ngx_buf_t *hbuf); +static ngx_chain_t *ngx_http_v3_create_data_frame(ngx_http_request_t *r, + ngx_chain_t *body, size_t size); +static ngx_inline ngx_uint_t ngx_http_v3_map_method(ngx_uint_t method); +static ngx_int_t ngx_http_v3_proxy_encode_method(ngx_http_request_t *r, + ngx_http_v3_proxy_ctx_t *v3c, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_proxy_encode_authority(ngx_http_request_t *r, + ngx_http_v3_proxy_ctx_t *v3c, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_proxy_encode_path(ngx_http_request_t *r, + ngx_http_v3_proxy_ctx_t *v3c, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_proxy_encode_headers(ngx_http_request_t *r, + ngx_http_v3_proxy_ctx_t *v3c, ngx_buf_t *b); +static ngx_int_t ngx_http_v3_proxy_body_length(ngx_http_request_t *r, + ngx_http_v3_proxy_ctx_t *v3c); +static ngx_chain_t *ngx_http_v3_proxy_encode_body(ngx_http_request_t *r, + ngx_http_v3_proxy_ctx_t *v3c); +static ngx_int_t ngx_http_v3_proxy_body_output_filter(void *data, + ngx_chain_t *in); + +static ngx_int_t ngx_http_v3_proxy_reinit_request(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_proxy_process_status_line(ngx_http_request_t *r); +static void ngx_http_v3_proxy_abort_request(ngx_http_request_t *r); +static void ngx_http_v3_proxy_finalize_request(ngx_http_request_t *r, + ngx_int_t rc); +static ngx_int_t ngx_http_v3_proxy_process_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); + +static ngx_int_t ngx_http_v3_proxy_headers_done(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_proxy_process_pseudo_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_proxy_input_filter_init(void *data); +static ngx_int_t ngx_http_v3_proxy_copy_filter(ngx_event_pipe_t *p, + ngx_buf_t *buf); +static ngx_int_t ngx_http_v3_proxy_non_buffered_copy_filter(void *data, + ssize_t bytes); +static ngx_int_t ngx_http_v3_proxy_construct_cookie_header( + ngx_http_request_t *r); + +static ngx_str_t ngx_http_v3_proxy_quic_salt = ngx_string("ngx_quic"); +#endif + static ngx_conf_post_t ngx_http_proxy_lowat_post = { ngx_http_proxy_lowat_check }; @@ -297,6 +378,7 @@ static ngx_conf_post_t ngx_http_proxy_s static ngx_conf_enum_t ngx_http_proxy_http_version[] = { { ngx_string("1.0"), NGX_HTTP_VERSION_10 }, { ngx_string("1.1"), NGX_HTTP_VERSION_11 }, + { ngx_string("3"), NGX_HTTP_VERSION_30 }, { ngx_null_string, 0 } }; @@ -688,8 +770,10 @@ static ngx_command_t ngx_http_proxy_com offsetof(ngx_http_proxy_loc_conf_t, upstream.ignore_headers), &ngx_http_upstream_ignore_headers_masks }, + /* also allowed in all places where "proxy_pass" can happen */ { ngx_string("proxy_http_version"), - NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF + |NGX_HTTP_LIF_CONF|NGX_HTTP_LMT_CONF|NGX_CONF_TAKE1, ngx_conf_set_enum_slot, NGX_HTTP_LOC_CONF_OFFSET, offsetof(ngx_http_proxy_loc_conf_t, http_version), @@ -790,6 +874,53 @@ static ngx_command_t ngx_http_proxy_com #endif +#if (NGX_HTTP_V3) + + { ngx_string("proxy_http3_max_concurrent_streams"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_proxy_loc_conf_t, + upstream.quic.max_concurrent_streams_bidi), + NULL }, + + { ngx_string("proxy_http3_stream_buffer_size"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_size_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_proxy_loc_conf_t, upstream.quic.stream_buffer_size), + NULL }, + + { ngx_string("proxy_quic_gso"), + 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_proxy_loc_conf_t, upstream.quic.gso_enabled), + NULL }, + + { ngx_string("proxy_quic_host_key"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_http_v3_proxy_host_key, + NGX_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { ngx_string("proxy_quic_active_connection_id_limit"), + NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1, + ngx_conf_set_num_slot, + NGX_HTTP_LOC_CONF_OFFSET, + offsetof(ngx_http_proxy_loc_conf_t, + upstream.quic.active_connection_id_limit), + NULL }, + + { ngx_string("proxy_http3_hq"), + 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_proxy_loc_conf_t, enable_hq), + NULL }, +#endif + ngx_null_command }; @@ -842,6 +973,56 @@ static ngx_keyval_t ngx_http_proxy_head }; +#if (NGX_HTTP_V3) + +/* + * RFC 9114 4.2 HTTP Fields + * + * An intermediary transforming an HTTP/1.x message to HTTP/3 MUST remove + * connection-specific header fields as discussed in Section 7.6.1 of [HTTP], + * or their messages will be treated by other HTTP/3 endpoints as malformed. + */ +static ngx_keyval_t ngx_http_v3_proxy_headers[] = { + { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") }, +#if 0 + /* TODO: trailers */ + { ngx_string("TE"), ngx_string("$v3_proxy_internal_trailers") }, +#endif + { ngx_string("Host"), ngx_string("") }, + { ngx_string("Connection"), ngx_string("") }, + { ngx_string("Transfer-Encoding"), ngx_string("") }, + { ngx_string("Keep-Alive"), ngx_string("") }, + { ngx_string("Expect"), ngx_string("") }, + { ngx_string("Upgrade"), ngx_string("") }, + { ngx_null_string, ngx_null_string } +}; + +#if (NGX_HTTP_CACHE) + +static ngx_keyval_t ngx_http_v3_proxy_cache_headers[] = { + { ngx_string("Host"), ngx_string("") }, + { ngx_string("Connection"), ngx_string("") }, + { ngx_string("Content-Length"), ngx_string("$proxy_internal_body_length") }, + { ngx_string("Transfer-Encoding"), ngx_string("") }, + { ngx_string("TE"), ngx_string("") }, + { ngx_string("Keep-Alive"), ngx_string("") }, + { ngx_string("Expect"), ngx_string("") }, + { ngx_string("Upgrade"), ngx_string("") }, + { ngx_string("If-Modified-Since"), + ngx_string("$upstream_cache_last_modified") }, + { ngx_string("If-Unmodified-Since"), ngx_string("") }, + { ngx_string("If-None-Match"), ngx_string("$upstream_cache_etag") }, + { ngx_string("If-Match"), ngx_string("") }, + { ngx_string("Range"), ngx_string("") }, + { ngx_string("If-Range"), ngx_string("") }, + { ngx_null_string, ngx_null_string } +}; + +#endif + +#endif + + static ngx_str_t ngx_http_proxy_hide_headers[] = { ngx_string("Date"), ngx_string("Server"), @@ -999,6 +1180,31 @@ ngx_http_proxy_handler(ngx_http_request_ u->process_header = ngx_http_proxy_process_status_line; u->abort_request = ngx_http_proxy_abort_request; u->finalize_request = ngx_http_proxy_finalize_request; + +#if (NGX_HTTP_V3) + if (plcf->http_version == NGX_HTTP_VERSION_30) { + + u->h3 = 1; + u->peer.type = SOCK_DGRAM; + + if (plcf->enable_hq) { + u->hq = 1; + + } else { + u->create_request = ngx_http_v3_proxy_create_request; + u->reinit_request = ngx_http_v3_proxy_reinit_request; + u->process_header = ngx_http_v3_proxy_process_status_line; + u->abort_request = ngx_http_v3_proxy_abort_request; + u->finalize_request = ngx_http_v3_proxy_finalize_request; + } + + ctx->v3_parse = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_t)); + if (ctx->v3_parse == NULL) { + return NGX_ERROR; + } + } +#endif + r->state = 0; if (plcf->redirects) { @@ -1017,10 +1223,20 @@ ngx_http_proxy_handler(ngx_http_request_ } u->pipe->input_filter = ngx_http_proxy_copy_filter; - u->pipe->input_ctx = r; u->input_filter_init = ngx_http_proxy_input_filter_init; u->input_filter = ngx_http_proxy_non_buffered_copy_filter; + +#if (NGX_HTTP_V3) + if (plcf->http_version == NGX_HTTP_VERSION_30 && !plcf->enable_hq) { + u->pipe->input_filter = ngx_http_v3_proxy_copy_filter; + + u->input_filter_init = ngx_http_v3_proxy_input_filter_init; + u->input_filter = ngx_http_v3_proxy_non_buffered_copy_filter; + } +#endif + + u->pipe->input_ctx = r; u->input_filter_ctx = r; u->accel = 1; @@ -1028,7 +1244,11 @@ ngx_http_proxy_handler(ngx_http_request_ if (!plcf->upstream.request_buffering && plcf->body_values == NULL && plcf->upstream.pass_request_body && (!r->headers_in.chunked - || plcf->http_version == NGX_HTTP_VERSION_11)) + || (plcf->http_version == NGX_HTTP_VERSION_11 +#if (NGX_HTTP_V3) + || plcf->http_version == NGX_HTTP_VERSION_30 +#endif + ))) { r->request_body_no_buffering = 1; } @@ -1066,6 +1286,13 @@ ngx_http_proxy_eval(ngx_http_request_t * { add = 7; port = 80; +#if (NGX_HTTP_V3) + if (plcf->http_version == NGX_HTTP_VERSION_30) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "http/3 requires https prefix"); + return NGX_ERROR; + } +#endif #if (NGX_HTTP_SSL) @@ -1473,6 +1700,12 @@ ngx_http_proxy_create_request(ngx_http_r u->uri.len = b->last - u->uri.data; +#if (NGX_HTTP_V3) + if (plcf->http_version == NGX_HTTP_VERSION_30 && plcf->enable_hq) { + goto nover; + } +#endif + if (plcf->http_version == NGX_HTTP_VERSION_11) { b->last = ngx_cpymem(b->last, ngx_http_proxy_version_11, sizeof(ngx_http_proxy_version_11) - 1); @@ -1482,6 +1715,10 @@ ngx_http_proxy_create_request(ngx_http_r sizeof(ngx_http_proxy_version) - 1); } +#if (NGX_HTTP_V3) +nover: +#endif + ngx_memzero(&e, sizeof(ngx_http_script_engine_t)); e.ip = headers->values->elts; @@ -1860,6 +2097,24 @@ ngx_http_proxy_process_status_line(ngx_h #endif +#if (NGX_HTTP_V3) + { + + ngx_http_proxy_loc_conf_t *plcf; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + + if (plcf->http_version == NGX_HTTP_VERSION_30 && plcf->enable_hq) { + r->http_version = NGX_HTTP_VERSION_9; + u->state->status = NGX_HTTP_OK; + u->headers_in.connection_close = 1; + + return NGX_OK; + } + + } +#endif + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent no valid HTTP/1.0 header"); @@ -3383,6 +3638,13 @@ ngx_http_proxy_create_loc_conf(ngx_conf_ * * conf->host = { 0, NULL }; * conf->host_set = 0; + * + * conf->upstream.quic.host_key = { 0, NULL } + * conf->upstream.quic.stream_reject_code_uni = 0; + * conf->upstream.quic.disable_active_migration = 0; + * conf->upstream.quic.idle_timeout = 0; + * conf->upstream.quic.handshake_timeout = 0; + * conf->upstream.quic.retry = 0; */ conf->upstream.store = NGX_CONF_UNSET; @@ -3466,6 +3728,26 @@ ngx_http_proxy_create_loc_conf(ngx_conf_ ngx_str_set(&conf->upstream.module, "proxy"); +#if (NGX_HTTP_V3) + + conf->upstream.quic.stream_buffer_size = NGX_CONF_UNSET_SIZE; + conf->upstream.quic.max_concurrent_streams_bidi = NGX_CONF_UNSET_UINT; + conf->upstream.quic.max_concurrent_streams_uni = + NGX_HTTP_V3_MAX_UNI_STREAMS; + conf->upstream.quic.gso_enabled = NGX_CONF_UNSET; + + conf->upstream.quic.active_connection_id_limit = NGX_CONF_UNSET_UINT; + + conf->upstream.quic.stream_close_code = NGX_HTTP_V3_ERR_NO_ERROR; + conf->upstream.quic.stream_reject_code_bidi = + NGX_HTTP_V3_ERR_REQUEST_REJECTED; + + conf->upstream.quic.shutdown = ngx_http_v3_shutdown; + + conf->enable_hq = NGX_CONF_UNSET; + +#endif + return conf; } @@ -3479,6 +3761,7 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t u_char *p; size_t size; ngx_int_t rc; + ngx_keyval_t *proxy_headers; ngx_hash_init_t hash; ngx_http_core_loc_conf_t *clcf; ngx_http_proxy_rewrite_t *pr; @@ -3883,6 +4166,16 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t return NGX_CONF_ERROR; } +#if (NGX_HTTP_V3) + + if (conf->http_version == NGX_HTTP_VERSION_30) { + if (ngx_http_v3_proxy_merge_quic(cf, conf, prev) != NGX_OK) { + return NGX_CONF_ERROR; + } + } + +#endif + clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module); if (clcf->noname @@ -3935,7 +4228,15 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t ngx_conf_merge_ptr_value(conf->headers_source, prev->headers_source, NULL); - if (conf->headers_source == prev->headers_source) { + if (conf->headers_source == prev->headers_source +#if (NGX_HTTP_V3) + /* H3 uses own set of headers, so do not inherit on version change */ + && !((conf->http_version == NGX_HTTP_VERSION_30 + || prev->http_version == NGX_HTTP_VERSION_30) + && conf->http_version != prev->http_version) +#endif + ) + { conf->headers = prev->headers; #if (NGX_HTTP_CACHE) conf->headers_cache = prev->headers_cache; @@ -3946,8 +4247,15 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t #endif } - rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers, - ngx_http_proxy_headers); + proxy_headers = ngx_http_proxy_headers; + +#if (NGX_HTTP_V3) + if (conf->http_version == NGX_HTTP_VERSION_30) { + proxy_headers = ngx_http_v3_proxy_headers; + } +#endif + + rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers, proxy_headers); if (rc != NGX_OK) { return NGX_CONF_ERROR; } @@ -3955,8 +4263,17 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t #if (NGX_HTTP_CACHE) if (conf->upstream.cache) { + + proxy_headers = ngx_http_proxy_cache_headers; + +#if (NGX_HTTP_V3) + if (conf->http_version == NGX_HTTP_VERSION_30) { + proxy_headers = ngx_http_v3_proxy_cache_headers; + } +#endif + rc = ngx_http_proxy_init_headers(cf, conf, &conf->headers_cache, - ngx_http_proxy_cache_headers); + proxy_headers); if (rc != NGX_OK) { return NGX_CONF_ERROR; } @@ -5060,6 +5377,12 @@ ngx_http_proxy_set_ssl(ngx_conf_t *cf, n return NGX_ERROR; } +#if (NGX_HTTP_V3 && NGX_QUIC_OPENSSL_COMPAT) + if (ngx_quic_compat_init(cf, plcf->upstream.ssl->ctx) != NGX_OK) { + return NGX_ERROR; + } +#endif + cln = ngx_pool_cleanup_add(cf->pool, 0); if (cln == NULL) { ngx_ssl_cleanup_ctx(plcf->upstream.ssl); @@ -5178,3 +5501,1942 @@ ngx_http_proxy_set_vars(ngx_url_t *u, ng v->uri = u->uri; } + + +#if (NGX_HTTP_V3) + +static char * +ngx_http_v3_proxy_host_key(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) +{ + ngx_http_proxy_loc_conf_t *plcf = 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 = &plcf->upstream.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; +} + + +static ngx_int_t +ngx_http_v3_proxy_merge_quic(ngx_conf_t *cf, ngx_http_proxy_loc_conf_t *conf, + ngx_http_proxy_loc_conf_t *prev) +{ + if ((conf->upstream.upstream || conf->proxy_lengths) + && (conf->ssl == 0 || conf->upstream.ssl == NULL)) + { + /* we have proxy_pass, http/3 and no ssl - this isn't going to work */ + + ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, + "http3 proxy requires ssl configuration " + "and https:// scheme"); + return NGX_ERROR; + } + + ngx_conf_merge_value(conf->enable_hq, prev->enable_hq, 0); + + if (conf->enable_hq) { + conf->upstream.quic.alpn.data = (unsigned char *) + NGX_HTTP_V3_HQ_ALPN_PROTO; + + conf->upstream.quic.alpn.len = sizeof(NGX_HTTP_V3_HQ_ALPN_PROTO) - 1; + + } else { + conf->upstream.quic.alpn.data = (unsigned char *) + NGX_HTTP_V3_ALPN_PROTO; + + conf->upstream.quic.alpn.len = sizeof(NGX_HTTP_V3_ALPN_PROTO) - 1; + } + + ngx_conf_merge_size_value(conf->upstream.quic.stream_buffer_size, + prev->upstream.quic.stream_buffer_size, + 65536); + + ngx_conf_merge_uint_value(conf->upstream.quic.max_concurrent_streams_bidi, + prev->upstream.quic.max_concurrent_streams_bidi, + 128); + + ngx_conf_merge_value(conf->upstream.quic.gso_enabled, + prev->upstream.quic.gso_enabled, + 0); + + ngx_conf_merge_uint_value(conf->upstream.quic.active_connection_id_limit, + prev->upstream.quic.active_connection_id_limit, + 2); + + conf->upstream.quic.idle_timeout = conf->upstream.read_timeout; + conf->upstream.quic.handshake_timeout = conf->upstream.connect_timeout; + + if (conf->upstream.quic.host_key.len == 0) { + + conf->upstream.quic.host_key.len = NGX_QUIC_DEFAULT_HOST_KEY_LEN; + conf->upstream.quic.host_key.data = ngx_palloc(cf->pool, + conf->upstream.quic.host_key.len); + + if (conf->upstream.quic.host_key.data == NULL) { + return NGX_ERROR; + } + + if (RAND_bytes(conf->upstream.quic.host_key.data, + NGX_QUIC_DEFAULT_HOST_KEY_LEN) + <= 0) + { + return NGX_ERROR; + } + } + + if (ngx_quic_derive_key(cf->log, "av_token_key", + &conf->upstream.quic.host_key, + &ngx_http_v3_proxy_quic_salt, + conf->upstream.quic.av_token_key, + NGX_QUIC_AV_KEY_LEN) + != NGX_OK) + { + return NGX_ERROR; + } + + if (ngx_quic_derive_key(cf->log, "sr_token_key", + &conf->upstream.quic.host_key, + &ngx_http_v3_proxy_quic_salt, + conf->upstream.quic.sr_token_key, + NGX_QUIC_SR_KEY_LEN) + != NGX_OK) + { + return NGX_ERROR; + } + + conf->upstream.quic.ssl = conf->upstream.ssl; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_create_request(ngx_http_request_t *r) +{ + ngx_buf_t *b; + ngx_chain_t *cl, *body, *out; + ngx_http_upstream_t *u; + ngx_http_proxy_ctx_t *ctx; + ngx_http_v3_proxy_ctx_t v3c; + ngx_http_proxy_headers_t *headers; + ngx_http_proxy_loc_conf_t *plcf; + + /* + * HTTP/3 Request: + * + * HEADERS FRAME + * :method: + * :scheme: + * :path: + * :authority: + * proxy headers[] + * client headers[] + * + * DATA FRAME + * body + * + * HEADERS FRAME + * trailers[] + */ + + u = r->upstream; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + +#if (NGX_HTTP_CACHE) + headers = u->cacheable ? &plcf->headers_cache : &plcf->headers; +#else + headers = &plcf->headers; +#endif + + ngx_memzero(&v3c, sizeof(ngx_http_v3_proxy_ctx_t)); + + ngx_http_script_flush_no_cacheable_variables(r, plcf->body_flushes); + ngx_http_script_flush_no_cacheable_variables(r, headers->flushes); + + v3c.headers = headers; + + v3c.n = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + /* calculate lengths */ + + ngx_http_v3_proxy_encode_method(r, &v3c, NULL); + + v3c.n += ngx_http_v3_encode_field_ri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + + if (ngx_http_v3_proxy_encode_path(r, &v3c, NULL) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_v3_proxy_encode_authority(r, &v3c, NULL) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_v3_proxy_body_length(r, &v3c) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_v3_proxy_encode_headers(r, &v3c, NULL) != NGX_OK) { + return NGX_ERROR; + } + + /* generate HTTP/3 request of known size */ + + b = ngx_create_temp_buf(r->pool, v3c.n); + if (b == NULL) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, + 0, 0, 0); + + if (ngx_http_v3_proxy_encode_method(r, &v3c, b) != NGX_OK) { + return NGX_ERROR; + } + + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + + if (ngx_http_v3_proxy_encode_path(r, &v3c, b) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_v3_proxy_encode_authority(r, &v3c, b) != NGX_OK) { + return NGX_ERROR; + } + + if (ngx_http_v3_proxy_encode_headers(r, &v3c, b) != NGX_OK) { + return NGX_ERROR; + } + + out = ngx_http_v3_create_headers_frame(r, b); + if (out == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + if (r->request_body_no_buffering || ctx->internal_chunked) { + u->output.output_filter = ngx_http_v3_proxy_body_output_filter; + u->output.filter_ctx = r; + + } else if (ctx->internal_body_length != -1) { + + body = ngx_http_v3_proxy_encode_body(r, &v3c); + if (body == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + body = ngx_http_v3_create_data_frame(r, body, + ctx->internal_body_length); + if (body == NGX_CHAIN_ERROR) { + return NGX_ERROR; + } + + for (cl = out; cl->next; cl = cl->next) { /* void */ } + cl->next = body; + } + + /* TODO: trailers */ + + u->request_bufs = out; + + return NGX_OK; +} + + +static ngx_chain_t * +ngx_http_v3_create_headers_frame(ngx_http_request_t *r, ngx_buf_t *hbuf) +{ + ngx_buf_t *b; + size_t n, len; + ngx_chain_t *cl, *head; + + n = hbuf->last - hbuf->pos; + + 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_CHAIN_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); + + /* mark our header buffers to distinguish them in non-buffered filter */ + b->tag = (ngx_buf_tag_t) &ngx_http_v3_create_headers_frame; + hbuf->tag = (ngx_buf_tag_t) &ngx_http_v3_create_headers_frame; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = b; + head = cl; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = hbuf; + cl->next = NULL; + + head->next = cl; + + return head; +} + + +static ngx_chain_t * +ngx_http_v3_create_data_frame(ngx_http_request_t *r, ngx_chain_t *body, + size_t size) +{ + size_t len; + ngx_buf_t *b; + ngx_chain_t *cl; + + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + + ngx_http_v3_encode_varlen_int(NULL, size); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NGX_CHAIN_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, size); + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = b; + cl->next = body; + + return cl; +} + + +static ngx_inline ngx_uint_t +ngx_http_v3_map_method(ngx_uint_t method) +{ + switch (method) { + case NGX_HTTP_GET: + return NGX_HTTP_V3_HEADER_METHOD_GET; + case NGX_HTTP_HEAD: + return NGX_HTTP_V3_HEADER_METHOD_HEAD; + case NGX_HTTP_POST: + return NGX_HTTP_V3_HEADER_METHOD_POST; + case NGX_HTTP_PUT: + return NGX_HTTP_V3_HEADER_METHOD_PUT; + case NGX_HTTP_DELETE: + return NGX_HTTP_V3_HEADER_METHOD_DELETE; + case NGX_HTTP_OPTIONS: + return NGX_HTTP_V3_HEADER_METHOD_OPTIONS; + default: + return 0; + } +} + + +static ngx_int_t +ngx_http_v3_proxy_encode_method(ngx_http_request_t *r, + ngx_http_v3_proxy_ctx_t *v3c, ngx_buf_t *b) +{ + size_t n; + ngx_str_t method; + ngx_uint_t v3method; + ngx_http_upstream_t *u; + ngx_http_proxy_ctx_t *ctx; + ngx_http_proxy_loc_conf_t *plcf; + + static ngx_str_t ngx_http_v3_header_method = ngx_string(":method"); + + if (b == NULL) { + /* calculate length */ + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + method.len = 0; + n = 0; + + u = r->upstream; + + if (u->method.len) { + /* HEAD was changed to GET to cache response */ + method = u->method; + + } else if (plcf->method) { + if (ngx_http_complex_value(r, plcf->method, &method) != NGX_OK) { + return NGX_ERROR; + } + } else { + method = r->method_name; + } + + if (method.len == 4 + && ngx_strncasecmp(method.data, (u_char *) "HEAD", 4) == 0) + { + ctx->head = 1; + } + + if (method.len) { + n = ngx_http_v3_encode_field_l(NULL, &ngx_http_v3_header_method, + &method); + } else { + + v3method = ngx_http_v3_map_method(r->method); + + if (v3method) { + n = ngx_http_v3_encode_field_ri(NULL, 0, v3method); + + } else { + n = ngx_http_v3_encode_field_l(NULL, + &ngx_http_v3_header_method, + &r->method_name); + } + } + + v3c->n += n; + v3c->method = method; + + return NGX_OK; + } + + method = v3c->method; + + if (method.len) { + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &ngx_http_v3_header_method, + &method); + } else { + + v3method = ngx_http_v3_map_method(r->method); + + if (v3method) { + b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, + v3method); + } else { + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &ngx_http_v3_header_method, + &r->method_name); + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_encode_authority(ngx_http_request_t *r, + ngx_http_v3_proxy_ctx_t *v3c, ngx_buf_t *b) +{ + size_t n; + ngx_http_proxy_ctx_t *ctx; + ngx_http_proxy_loc_conf_t *plcf; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + + if (plcf->host_set) { + return NGX_OK; + } + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + if (b == NULL) { + + n = ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_AUTHORITY, + NULL, ctx->host.len); + v3c->n += n; + + return NGX_OK; + } + + b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, + NGX_HTTP_V3_HEADER_AUTHORITY, ctx->host.data, ctx->host.len); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \":authority: %V\"", &ctx->host); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_encode_path(ngx_http_request_t *r, + ngx_http_v3_proxy_ctx_t *v3c, ngx_buf_t *b) +{ + size_t n; + u_char *p; + size_t loc_len; + size_t uri_len; + ngx_str_t tmp; + uintptr_t escape; + ngx_uint_t unparsed_uri; + ngx_http_upstream_t *u; + ngx_http_proxy_ctx_t *ctx; + ngx_http_proxy_loc_conf_t *plcf; + + static ngx_str_t ngx_http_v3_path = ngx_string(":path"); + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + if (b == NULL) { + + escape = 0; + uri_len = 0; + loc_len = 0; + unparsed_uri = 0; + + if (plcf->proxy_lengths && ctx->vars.uri.len) { + uri_len = ctx->vars.uri.len; + + } else if (ctx->vars.uri.len == 0 && r->valid_unparsed_uri) { + unparsed_uri = 1; + uri_len = r->unparsed_uri.len; + + } else { + loc_len = (r->valid_location && ctx->vars.uri.len) ? + plcf->location.len : 0; + + if (r->quoted_uri || r->internal) { + escape = 2 * ngx_escape_uri(NULL, r->uri.data + loc_len, + r->uri.len - loc_len, + NGX_ESCAPE_URI); + } + + uri_len = ctx->vars.uri.len + r->uri.len - loc_len + escape + + sizeof("?") - 1 + r->args.len; + } + + if (uri_len == 0) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "zero length URI to proxy"); + return NGX_ERROR; + } + + tmp.data = NULL; + tmp.len = uri_len; + + n = ngx_http_v3_encode_field_l(NULL, &ngx_http_v3_path, &tmp); + + v3c->n += n; + + v3c->escape = escape; + v3c->uri_len = uri_len; + v3c->loc_len = loc_len; + v3c->unparsed_uri = unparsed_uri; + + return NGX_OK; + } + + u = r->upstream; + + escape = v3c->escape; + uri_len = v3c->uri_len; + loc_len = v3c->loc_len; + unparsed_uri = v3c->unparsed_uri; + + p = ngx_palloc(r->pool, uri_len); + if (p == NULL) { + return NGX_ERROR; + } + + u->uri.data = p; + + if (plcf->proxy_lengths && ctx->vars.uri.len) { + p = ngx_copy(p, ctx->vars.uri.data, ctx->vars.uri.len); + + } else if (unparsed_uri) { + p = ngx_copy(p, r->unparsed_uri.data, r->unparsed_uri.len); + + } else { + if (r->valid_location) { + p = ngx_copy(p, ctx->vars.uri.data, ctx->vars.uri.len); + } + + if (escape) { + ngx_escape_uri(p, r->uri.data + loc_len, + r->uri.len - loc_len, NGX_ESCAPE_URI); + p += r->uri.len - loc_len + escape; + + } else { + p = ngx_copy(p, r->uri.data + loc_len, r->uri.len - loc_len); + } + + if (r->args.len > 0) { + *p++ = '?'; + p = ngx_copy(p, r->args.data, r->args.len); + } + } + + u->uri.len = p - u->uri.data; + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, &ngx_http_v3_path, + &u->uri); + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_body_length(ngx_http_request_t *r, + ngx_http_v3_proxy_ctx_t *v3c) +{ + size_t body_len, n; + ngx_http_proxy_ctx_t *ctx; + ngx_http_script_engine_t *le; + ngx_http_proxy_loc_conf_t *plcf; + ngx_http_script_len_code_pt lcode; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + le = &v3c->le; + + n = 0; + + if (plcf->body_lengths) { + le->ip = plcf->body_lengths->elts; + le->request = r; + le->flushed = 1; + body_len = 0; + + while (*(uintptr_t *) le->ip) { + lcode = *(ngx_http_script_len_code_pt *) le->ip; + body_len += lcode(le); + } + + ctx->internal_body_length = body_len; + n += body_len; + + } else if (r->headers_in.chunked && r->reading_body) { + ctx->internal_body_length = -1; + ctx->internal_chunked = 1; + + } else { + ctx->internal_body_length = r->headers_in.content_length_n; + n = r->headers_in.content_length_n; + } + + v3c->n += n; + + return NGX_OK; +} + + +static ngx_chain_t * +ngx_http_v3_proxy_encode_body(ngx_http_request_t *r, + ngx_http_v3_proxy_ctx_t *v3c) +{ + ngx_buf_t *b; + ngx_chain_t *body, *cl, *prev, *head; + ngx_http_upstream_t *u; + ngx_http_proxy_ctx_t *ctx; + ngx_http_script_code_pt code; + ngx_http_script_engine_t *e; + ngx_http_proxy_loc_conf_t *plcf; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + u = r->upstream; + + /* body set in configuration */ + + if (plcf->body_values) { + + e = &v3c->e; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + b = ngx_create_temp_buf(r->pool, ctx->internal_body_length); + if (b == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = b; + cl->next = NULL; + + e->ip = plcf->body_values->elts; + e->pos = b->last; + e->skip = 0; + + while (*(uintptr_t *) e->ip) { + code = *(ngx_http_script_code_pt *) e->ip; + code((ngx_http_script_engine_t *) e); + } + + b->last = e->pos; + + return cl; + } + + if (!plcf->upstream.pass_request_body) { + return NULL; + } + + /* body from client */ + + cl = NULL; + head = NULL; + prev = NULL; + + body = u->request_bufs; + + while (body) { + + b = ngx_alloc_buf(r->pool); + if (b == NULL) { + return NGX_CHAIN_ERROR; + } + + ngx_memcpy(b, body->buf, sizeof(ngx_buf_t)); + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NGX_CHAIN_ERROR; + } + + cl->buf = b; + + if (prev) { + prev->next = cl; + + } else { + head = cl; + } + + prev = cl; + body = body->next; + } + + if (cl) { + cl->next = NULL; + } + + return head; +} + + +static ngx_int_t +ngx_http_v3_proxy_body_output_filter(void *data, ngx_chain_t *in) +{ + ngx_http_request_t *r = data; + + off_t size; + u_char *chunk; + size_t len; + ngx_buf_t *b; + ngx_int_t rc; + ngx_chain_t *out, *cl, *tl, **ll, **fl; + ngx_http_proxy_ctx_t *ctx; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "v3 proxy output filter"); + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + if (in == NULL) { + out = in; + goto out; + } + + out = NULL; + ll = &out; + + if (!ctx->header_sent) { + + /* buffers contain v3-encoded headers frame, pass it as is */ + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "v3 proxy output header"); + + ctx->header_sent = 1; + + for ( ;; ) { + + if (in->buf->tag + != (ngx_buf_tag_t) &ngx_http_v3_create_headers_frame) + { + break; + } + + tl = ngx_alloc_chain_link(r->pool); + if (tl == NULL) { + return NGX_ERROR; + } + + tl->buf = in->buf; + *ll = tl; + ll = &tl->next; + + in = in->next; + + if (in == NULL) { + tl->next = NULL; + goto out; + } + } + } + + size = 0; + fl = ll; + + for (cl = in; cl; cl = cl->next) { + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "v3 proxy output 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 (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) { + len = ngx_http_v3_encode_varlen_int(NULL, + NGX_HTTP_V3_FRAME_DATA) + + 8 /* max varlen int length*/; + + chunk = ngx_palloc(r->pool, len); + if (chunk == NULL) { + return NGX_ERROR; + } + b->start = chunk; + b->pos = b->start; + b->end = chunk + len; + } + + b->tag = (ngx_buf_tag_t) &ngx_http_v3_proxy_body_output_filter; + b->memory = 0; + b->temporary = 1; + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->start, + NGX_HTTP_V3_FRAME_DATA); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size); + + tl->next = *fl; + *fl = tl; + } + + *ll = NULL; + +out: + + rc = ngx_chain_writer(&r->upstream->writer, out); + + ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, + (ngx_buf_tag_t) &ngx_http_v3_proxy_body_output_filter); + + return rc; +} + + +static ngx_int_t +ngx_http_v3_proxy_encode_headers(ngx_http_request_t *r, + ngx_http_v3_proxy_ctx_t *v3c, ngx_buf_t *b) +{ + u_char *p, *start; + size_t key_len, val_len, hlen, max_head, n; + ngx_str_t tmp, tmpv; + ngx_uint_t i; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_http_script_code_pt code; + ngx_http_proxy_headers_t *headers; + ngx_http_script_engine_t *le; + ngx_http_script_engine_t *e; + ngx_http_proxy_loc_conf_t *plcf; + ngx_http_script_len_code_pt lcode; + + plcf = ngx_http_get_module_loc_conf(r, ngx_http_proxy_module); + + headers = v3c->headers; + le = &v3c->le; + e = &v3c->e; + + if (b == NULL) { + + le->ip = headers->lengths->elts; + le->request = r; + le->flushed = 1; + + n = 0; + max_head = 0; + + while (*(uintptr_t *) le->ip) { + + lcode = *(ngx_http_script_len_code_pt *) le->ip; + key_len = lcode(le); + + for (val_len = 0; *(uintptr_t *) le->ip; val_len += lcode(le)) { + lcode = *(ngx_http_script_len_code_pt *) le->ip; + } + le->ip += sizeof(uintptr_t); + + if (val_len == 0) { + continue; + } + + tmp.data = NULL; + tmp.len = key_len; + + tmpv.data = NULL; + tmpv.len = val_len; + + hlen = key_len + val_len; + if (hlen > max_head) { + max_head = hlen; + } + + n += ngx_http_v3_encode_field_l(NULL, &tmp, &tmpv); + } + + if (plcf->upstream.pass_request_headers) { + 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; + } + + if (ngx_hash_find(&headers->hash, header[i].hash, + header[i].lowcase_key, header[i].key.len)) + { + continue; + } + + n += ngx_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + } + + v3c->n += n; + v3c->max_head = max_head; + + return NGX_OK; + } + + max_head = v3c->max_head; + + p = ngx_pnalloc(r->pool, max_head); + if (p == NULL) { + return NGX_ERROR; + } + + start = p; + + ngx_memzero(e, sizeof(ngx_http_script_engine_t)); + + e->ip = headers->values->elts; + e->pos = p; + e->request = r; + e->flushed = 1; + + le->ip = headers->lengths->elts; + + tmp.data = p; + tmp.len = 0; + + tmpv.data = NULL; + tmpv.len = 0; + + while (*(uintptr_t *) le->ip) { + + lcode = *(ngx_http_script_len_code_pt *) le->ip; + (void) lcode(le); + + for (val_len = 0; *(uintptr_t *) le->ip; val_len += lcode(le)) { + lcode = *(ngx_http_script_len_code_pt *) le->ip; + } + le->ip += sizeof(uintptr_t); + + if (val_len == 0) { + e->skip = 1; + + while (*(uintptr_t *) e->ip) { + code = *(ngx_http_script_code_pt *) e->ip; + code((ngx_http_script_engine_t *) e); + } + e->ip += sizeof(uintptr_t); + + e->skip = 0; + + continue; + } + + code = *(ngx_http_script_code_pt *) e->ip; + code((ngx_http_script_engine_t *) e); + + tmp.len = e->pos - tmp.data; + tmpv.data = e->pos; + + while (*(uintptr_t *) e->ip) { + code = *(ngx_http_script_code_pt *) e->ip; + code((ngx_http_script_engine_t *) e); + } + e->ip += sizeof(uintptr_t); + + tmpv.len = e->pos - tmpv.data; + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, &tmp, &tmpv); + + tmp.data = p; + tmp.len = 0; + + tmpv.data = NULL; + tmpv.len = 0; + e->pos = start; + } + + if (plcf->upstream.pass_request_headers) { + 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; + } + + if (ngx_hash_find(&headers->hash, header[i].hash, + header[i].lowcase_key, header[i].key.len)) + { + continue; + } + + b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \"%V: %V\"", + &header[i].key, &header[i].value); + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_reinit_request(ngx_http_request_t *r) +{ + ngx_http_proxy_ctx_t *ctx; + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + if (ctx == NULL) { + return NGX_OK; + } + + r->upstream->process_header = ngx_http_v3_proxy_process_status_line; + r->upstream->pipe->input_filter = ngx_http_v3_proxy_copy_filter; + r->upstream->input_filter = ngx_http_v3_proxy_non_buffered_copy_filter; + r->state = 0; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_process_status_line(ngx_http_request_t *r) +{ + u_char *p; + ngx_buf_t *b; + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_upstream_t *u; + ngx_http_proxy_ctx_t *ctx; + ngx_http_v3_session_t *h3c; + ngx_http_v3_parse_headers_t *st; +#if (NGX_HTTP_CACHE) + ngx_connection_t stub; +#endif + + u = r->upstream; + c = u->peer.connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "ngx_http_v3_proxy_process_status_line"); + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + if (ctx == NULL) { + return NGX_ERROR; + } + +#if (NGX_HTTP_CACHE) + if (r->cache) { + /* no connection here */ + h3c = NULL; + ngx_memzero(&stub, sizeof(ngx_connection_t)); + c = &stub; + + /* while HTTP/3 parsing, only log and pool are used */ + c->log = r->connection->log; + c->pool = r->connection->pool; + } else +#endif + + h3c = ngx_http_v3_get_session(c); + + if (ngx_list_init(&u->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + return NGX_ERROR; + } + + ctx->v3_parse->header_limit = u->conf->bufs.size * u->conf->bufs.num; + + st = &ctx->v3_parse->headers; + b = &u->buffer; + + for ( ;; ) { + + p = b->pos; + + rc = ngx_http_v3_parse_headers(c, st, b); + if (rc > 0) { + + if (h3c) { + ngx_quic_reset_stream(c, rc); + } + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid header rc:%i", rc); + return NGX_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (h3c) { + h3c->total_bytes += b->pos - p; + } + + if (rc == NGX_BUSY) { + /* HTTP/3 blocked */ + return NGX_AGAIN; + } + + if (rc == NGX_AGAIN) { + return NGX_AGAIN; + } + + /* rc == NGX_OK || rc == NGX_DONE */ + + if (h3c) { + h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL, + &st->field_rep.field.name, + &st->field_rep.field.value); + } + + if (ngx_http_v3_proxy_process_header(r, &st->field_rep.field.name, + &st->field_rep.field.value) + != NGX_OK) + { + return NGX_ERROR; + } + + if (rc == NGX_DONE) { + return ngx_http_v3_proxy_headers_done(r); + } + } + + return NGX_OK; +} + + +static void +ngx_http_v3_proxy_abort_request(ngx_http_request_t *r) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "abort http v3 proxy request"); +} + + +static void +ngx_http_v3_proxy_finalize_request(ngx_http_request_t *r, ngx_int_t rc) +{ + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "finalize http v3 proxy request"); +} + + +static ngx_int_t +ngx_http_v3_proxy_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_upstream_t *u; + ngx_http_proxy_ctx_t *ctx; + ngx_http_upstream_header_t *hh; + ngx_http_upstream_main_conf_t *umcf; + + /* based on ngx_http_v3_process_header() */ + + umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); + u = r->upstream; + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + len = name->len + value->len; + + if (len > ctx->v3_parse->header_limit) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too large header"); + return NGX_ERROR; + } + + ctx->v3_parse->header_limit -= len; + + if (name->len && name->data[0] == ':') { + return ngx_http_v3_proxy_process_pseudo_header(r, name, value); + } + + h = ngx_list_push(&u->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + /* + * HTTP/3 parsing used peer->connection.pool, which might be destroyed, + * at the moment when r->headers_out are used; + * thus allocate from r->pool and copy header name/value + */ + h->key.len = name->len; + h->key.data = ngx_pnalloc(r->pool, name->len + 1); + if (h->key.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(h->key.data, name->data, name->len); + h->key.data[h->key.len] = 0; + + h->value.len = value->len; + h->value.data = ngx_pnalloc(r->pool, value->len + 1); + if (h->value.data == NULL) { + return NGX_ERROR; + } + ngx_memcpy(h->value.data, value->data, value->len); + h->value.data[h->value.len] = 0; + + h->lowcase_key = h->key.data; + h->hash = ngx_hash_key(h->key.data, h->key.len); + + 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; + } + + 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_proxy_headers_done(ngx_http_request_t *r) +{ + ngx_table_elt_t *h; + ngx_connection_t *c; + ngx_http_proxy_ctx_t *ctx; + ngx_http_upstream_t *u; + + /* + * based on NGX_HTTP_PARSE_HEADER_DONE in ngx_http_proxy_process_header() + * and ngx_http_v3_process_request_header() + */ + + u = r->upstream; + c = u->peer.connection; + + /* + * if no "Server" and "Date" in header line, + * then add the special empty headers + */ + + if (u->headers_in.server == NULL) { + h = ngx_list_push(&u->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( + ngx_hash('s', 'e'), 'r'), 'v'), 'e'), 'r'); + + ngx_str_set(&h->key, "Server"); + ngx_str_null(&h->value); + h->lowcase_key = (u_char *) "server"; + h->next = NULL; + } + + if (u->headers_in.date == NULL) { + h = ngx_list_push(&u->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash('d', 'a'), 't'), 'e'); + + ngx_str_set(&h->key, "Date"); + ngx_str_null(&h->value); + h->lowcase_key = (u_char *) "date"; + h->next = NULL; + } + + if (ngx_http_v3_proxy_construct_cookie_header(r) != NGX_OK) { + return NGX_ERROR; + } + + if (u->headers_in.content_length) { + u->headers_in.content_length_n = + ngx_atoof(u->headers_in.content_length->value.data, + u->headers_in.content_length->value.len); + + if (u->headers_in.content_length_n == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent invalid \"Content-Length\" header"); + return NGX_ERROR; + } + + } else { + u->headers_in.content_length_n = -1; + } + + /* + * set u->keepalive if response has no body; this allows to keep + * connections alive in case of r->header_only or X-Accel-Redirect + */ + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT + || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED + || ctx->head + || (!u->headers_in.chunked + && u->headers_in.content_length_n == 0)) + { + u->keepalive = !u->headers_in.connection_close; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + ngx_int_t status; + ngx_str_t *status_line; + ngx_http_upstream_t *u; + + /* based on ngx_http_v3_process_pseudo_header() */ + + /* + * RFC 9114, 4.3.2 + * + * For responses, a single ":status" pseudo-header field + * is defined that carries the HTTP status code; + */ + + u = r->upstream; + + if (name->len == 7 && ngx_strncmp(name->data, ":status", 7) == 0) { + + if (u->state && u->state->status +#if (NGX_HTTP_CACHE) + && !r->cached +#endif + ) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "upstream sent duplicate \":status\" header"); + return NGX_ERROR; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "upstream sent empty \":status\" header"); + return NGX_ERROR; + } + + if (value->len < 3) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "upstream sent too short \":status\" header"); + return NGX_ERROR; + } + + status = ngx_atoi(value->data, 3); + + if (status == NGX_ERROR) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent invalid status \"%V\"", value); + return NGX_ERROR; + } + + if (u->state && u->state->status == 0) { + u->state->status = status; + } + + u->headers_in.status_n = status; + + status_line = ngx_http_status_line(status); + if (status_line) { + u->headers_in.status_line = *status_line; + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http v3 proxy status %ui \"%V\"", + u->headers_in.status_n, &u->headers_in.status_line); + + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "upstream sent unexpected pseudo-header \"%V\"", name); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_proxy_input_filter_init(void *data) +{ + ngx_http_request_t *r = data; + + ngx_http_upstream_t *u; + ngx_http_proxy_ctx_t *ctx; + + u = r->upstream; + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http v3 proxy filter init s:%ui h:%d c:%d l:%O", + u->headers_in.status_n, ctx->head, u->headers_in.chunked, + u->headers_in.content_length_n); + + /* as per RFC2616, 4.4 Message Length */ + + /* HTTP/3 is 'chunked-like' by default, filter is already set */ + + if (u->headers_in.status_n == NGX_HTTP_NO_CONTENT + || u->headers_in.status_n == NGX_HTTP_NOT_MODIFIED + || ctx->head) + { + /* 1xx, 204, and 304 and replies to HEAD requests */ + /* no 1xx since we don't send Expect and Upgrade */ + + u->pipe->length = 0; + u->length = 0; + + } else if (u->headers_in.content_length_n == 0) { + /* empty body: special case as filter won't be called */ + + u->pipe->length = 0; + u->length = 0; + + } else { + /* content length or connection close */ + + u->pipe->length = u->headers_in.content_length_n; + u->length = u->headers_in.content_length_n; + } + + /* TODO: check flag handling in HTTP/3 */ + u->keepalive = 1; + + return NGX_OK; +} + + +/* reading non-buffered body from V3 upstream */ +static ngx_int_t +ngx_http_v3_proxy_non_buffered_copy_filter(void *data, ssize_t bytes) +{ + ngx_http_request_t *r = data; + + size_t size, len; + ngx_int_t rc; + ngx_buf_t *b, *buf; + ngx_chain_t *cl, **ll; + ngx_http_upstream_t *u; + ngx_http_proxy_ctx_t *ctx; + ngx_http_v3_parse_data_t *st; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http v3 proxy non buffered copy filter"); + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + if (ctx == NULL) { + return NGX_ERROR; + } + + u = r->upstream; + buf = &u->buffer; + + buf->pos = buf->last; + buf->last += bytes; + + for (cl = u->out_bufs, ll = &u->out_bufs; cl; cl = cl->next) { + ll = &cl->next; + } + + st = &ctx->v3_parse->body; + + while (buf->pos < buf->last) { + + if (st->length == 0) { + + rc = ngx_http_v3_parse_data(r->connection, st, buf); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "ngx_http_v3_parse_data rc:%i st->length: %ui", + rc, st->length); + + if (rc == NGX_AGAIN) { + break; + } + + if (rc == NGX_ERROR || rc > 0) { + return NGX_ERROR; + } + + if (rc == NGX_DONE) { + /* TODO: trailers */ + u->length = 0; + } + + /* rc == NGX_OK */ + continue; + } + + /* need to consume ctx->st.length bytes and then parse again */ + + cl = ngx_chain_get_free_buf(r->pool, &u->free_bufs); + if (cl == NULL) { + return NGX_ERROR; + } + + *ll = cl; + ll = &cl->next; + + b = cl->buf; + + b->start = buf->pos; + b->pos = buf->pos; + b->last = buf->last; + b->end = buf->end; + + b->tag = u->output.tag; + b->flush = 1; + b->temporary = 1; + + size = buf->last - buf->pos; + len = ngx_min(size, st->length); + + buf->pos += len; + st->length -= len; + + if (u->length != -1) { + u->length -= len; + } + + b->last = buf->pos; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http v3 proxy out buf %p %z", + b->pos, b->last - b->pos); + } + + if (u->length == 0) { + u->keepalive = !u->headers_in.connection_close; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_copy_filter(ngx_event_pipe_t *p, ngx_buf_t *buf) +{ + size_t size, len; + ngx_int_t rc; + ngx_buf_t *b, **prev; + ngx_chain_t *cl; + ngx_http_upstream_t *u; + ngx_http_request_t *r; + ngx_http_proxy_ctx_t *ctx; + ngx_http_v3_parse_data_t *st; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, + "http_v3_proxy_copy_filter"); + + if (buf->pos == buf->last) { + return NGX_OK; + } + + if (p->upstream_done) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, p->log, 0, + "http v3 proxy data after close"); + return NGX_OK; + } + + if (p->length == 0) { + ngx_log_error(NGX_LOG_WARN, p->log, 0, + "upstream sent more data than specified in " + "\"Content-Length\" header"); + + r = p->input_ctx; + r->upstream->keepalive = 0; + p->upstream_done = 1; + + return NGX_OK; + } + + r = p->input_ctx; + u = r->upstream; + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + if (ctx == NULL) { + return NGX_ERROR; + } + + st = &ctx->v3_parse->body; + + b = NULL; + prev = &buf->shadow; + + while (buf->pos < buf->last) { + + if (st->length == 0) { + rc = ngx_http_v3_parse_data(r->connection, st, buf); + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "ngx_http_v3_parse_data rc:%i st->length: %ui", + rc, st->length); + + if (rc == NGX_AGAIN) { + break; + } + + if (rc == NGX_ERROR || rc > 0) { + return NGX_ERROR; + } + + if (rc == NGX_DONE) { + /* TODO: trailers */ + p->length = 0; + } + + /* rc == NGX_OK */ + continue; + } + + /* need to consume ctx->st.length bytes and then parse again */ + + cl = ngx_chain_get_free_buf(p->pool, &p->free); + if (cl == NULL) { + return NGX_ERROR; + } + + b = cl->buf; + + ngx_memcpy(b, buf, sizeof(ngx_buf_t)); + + b->tag = p->tag; + b->recycled = 1; + b->temporary = 1; + + *prev = b; + prev = &b->shadow; + + if (p->in) { + *p->last_in = cl; + + } else { + p->in = cl; + } + + p->last_in = &cl->next; + + size = buf->last - buf->pos; + + len = ngx_min(size, st->length); + + buf->pos += len; + b->last = buf->pos; + + st->length -= len; + ctx->data_recvd += len; + + if (p->length != -1) { + p->length -= len; + } + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0, + "http v3 proxy input buf #%d %p", b->num, b->pos); + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, p->log, 0, + "http v3 proxy copy filter st length %ui pipe len:%O", + st->length, p->length); + + if (p->length == 0) { + u->keepalive = !u->headers_in.connection_close; + } + + if (b) { + b->shadow = buf; + b->last_shadow = 1; + + ngx_log_debug2(NGX_LOG_DEBUG_EVENT, p->log, 0, + "input buf %p %z", b->pos, b->last - b->pos); + return NGX_OK; + } + + /* there is no data record in the buf, add it to free chain */ + + if (ngx_event_pipe_add_free_buf(p, buf) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_proxy_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_upstream_t *u; + ngx_http_proxy_ctx_t *ctx; + ngx_http_upstream_main_conf_t *umcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + ctx = ngx_http_get_module_ctx(r, ngx_http_proxy_module); + + u = r->upstream; + cookies = ctx->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) { + 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(&u->headers_in.headers); + if (h == NULL) { + 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; + + umcf = ngx_http_get_module_main_conf(r, ngx_http_upstream_module); + + hh = ngx_hash_find(&umcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + +#endif diff --git a/src/http/modules/ngx_http_upstream_keepalive_module.c b/src/http/modules/ngx_http_upstream_keepalive_module.c --- a/src/http/modules/ngx_http_upstream_keepalive_module.c +++ b/src/http/modules/ngx_http_upstream_keepalive_module.c @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Maxim Dounin * Copyright (C) Nginx, Inc. */ @@ -282,6 +283,17 @@ found: ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, "get keepalive peer: using connection %p", c); +#if (NGX_HTTP_V3) + if (c->quic) { + pc->connection = c->quic->parent; + pc->cached = 1; + + ngx_http_v3_upstream_close_request_stream(c, 1); + + return NGX_DONE; + } +#endif + c->idle = 0; c->sent = 0; c->data = NULL; @@ -308,6 +320,7 @@ ngx_http_upstream_free_keepalive_peer(ng ngx_http_upstream_keepalive_peer_data_t *kp = data; ngx_http_upstream_keepalive_cache_t *item; + ngx_uint_t requests; ngx_queue_t *q; ngx_connection_t *c; ngx_http_upstream_t *u; @@ -320,18 +333,38 @@ ngx_http_upstream_free_keepalive_peer(ng u = kp->upstream; c = pc->connection; + if (c == NULL) { + goto invalid; + } + if (state & NGX_PEER_FAILED || c == NULL +#if (NGX_HTTP_V3) + /* quic stream is done when using: ok to have EOF/write err */ + || (c->quic == NULL && c->read->eof) + || (c->quic == NULL && c->write->error) + || c->type == SOCK_DGRAM +#else || c->read->eof + || c->write->error +#endif || c->read->error || c->read->timedout - || c->write->error || c->write->timedout) { goto invalid; } - if (c->requests >= kp->conf->requests) { +#if (NGX_HTTP_V3) + if (c->quic) { + requests = c->quic->parent->requests; + + } else +#endif + + requests = c->requests; + + if (requests >= kp->conf->requests) { goto invalid; } @@ -464,6 +497,16 @@ close: static void ngx_http_upstream_keepalive_close(ngx_connection_t *c) { +#if (NGX_HTTP_V3) + ngx_connection_t *pc; + + if (c->quic) { + pc = c->quic->parent; + ngx_http_v3_upstream_close_request_stream(c, 1); + ngx_http_v3_shutdown(pc); + return; + } +#endif #if (NGX_HTTP_SSL) diff --git a/src/http/ngx_http_header_filter_module.c b/src/http/ngx_http_header_filter_module.c --- a/src/http/ngx_http_header_filter_module.c +++ b/src/http/ngx_http_header_filter_module.c @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ @@ -625,6 +626,55 @@ ngx_http_header_filter(ngx_http_request_ } +ngx_str_t * +ngx_http_status_line(ngx_uint_t status) +{ + ngx_str_t *status_line; + + if (status >= NGX_HTTP_OK + && status < NGX_HTTP_LAST_2XX) + { + /* 2XX */ + + status -= NGX_HTTP_OK; + status_line = &ngx_http_status_lines[status]; + + } else if (status >= NGX_HTTP_MOVED_PERMANENTLY + && status < NGX_HTTP_LAST_3XX) + { + /* 3XX */ + + status = status - NGX_HTTP_MOVED_PERMANENTLY + + NGX_HTTP_OFF_3XX; + + status_line = &ngx_http_status_lines[status]; + + } else if (status >= NGX_HTTP_BAD_REQUEST + && status < NGX_HTTP_LAST_4XX) + { + /* 4XX */ + status = status - NGX_HTTP_BAD_REQUEST + + NGX_HTTP_OFF_4XX; + + status_line = &ngx_http_status_lines[status]; + + } else if (status >= NGX_HTTP_INTERNAL_SERVER_ERROR + && status < NGX_HTTP_LAST_5XX) + { + /* 5XX */ + status = status - NGX_HTTP_INTERNAL_SERVER_ERROR + + NGX_HTTP_OFF_5XX; + + status_line = &ngx_http_status_lines[status]; + + } else { + return NULL; + } + + return status_line; +} + + static ngx_int_t ngx_http_header_filter_init(ngx_conf_t *cf) { diff --git a/src/http/ngx_http_request.h b/src/http/ngx_http_request.h --- a/src/http/ngx_http_request.h +++ b/src/http/ngx_http_request.h @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ @@ -610,6 +611,7 @@ typedef struct { #define ngx_http_ephemeral(r) (void *) (&r->uri_start) +ngx_str_t *ngx_http_status_line(ngx_uint_t status); extern ngx_http_header_t ngx_http_headers_in[]; extern ngx_http_header_out_t ngx_http_headers_out[]; diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -194,6 +194,23 @@ static ngx_int_t ngx_http_upstream_ssl_c ngx_http_upstream_t *u, ngx_connection_t *c); #endif +#if (NGX_HTTP_V3) +static ngx_int_t ngx_http_v3_upstream_init_connection(ngx_http_request_t *, + ngx_http_upstream_t *u, ngx_connection_t *c); +static ngx_int_t ngx_http_v3_upstream_init_ssl(ngx_connection_t *c, void *data); +static ngx_int_t ngx_http_v3_upstream_reuse_connection(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_connection_t *c); +static ngx_int_t ngx_http_v3_upstream_init_h3(ngx_connection_t *c, + ngx_http_request_t *r); +static void ngx_http_v3_upstream_connect_handler(ngx_event_t *ev); +static ngx_int_t ngx_http_v3_upstream_connected(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_connection_t *sc); +static ngx_int_t ngx_http_v3_upstream_send_request(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_connection_t *sc); +static void ngx_http_quic_upstream_dummy_handler(ngx_event_t *ev); +static void ngx_http_quic_stream_close_handler(ngx_event_t *ev); +#endif + static ngx_http_upstream_header_t ngx_http_upstream_headers_in[] = { @@ -1586,6 +1603,20 @@ ngx_http_upstream_connect(ngx_http_reque c->requests++; +#if (NGX_HTTP_V3) + + /* this is cached main quic connection with completed handshake */ + if (u->peer.cached && c->type == SOCK_DGRAM) { + if (ngx_http_v3_upstream_reuse_connection(r, u, c) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + return; + } + +#endif + c->data = r; c->write->handler = ngx_http_upstream_handler; @@ -1625,6 +1656,26 @@ ngx_http_upstream_connect(ngx_http_reque return; } +#if (NGX_HTTP_V3) + + if (u->h3) { + rc = ngx_http_v3_upstream_init_connection(r, u, c); + + if (rc == NGX_DECLINED) { + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); + return; + } + + if (rc != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + return; + } + +#endif + #if (NGX_HTTP_SSL) if (u->ssl && c->ssl == NULL) { @@ -1856,6 +1907,17 @@ ngx_http_upstream_ssl_save_session(ngx_c ngx_http_request_t *r; ngx_http_upstream_t *u; +#if (NGX_HTTP_V3) + if (c->udp) { + /* SSL callback is called on main quic connection */ + c = ngx_quic_client_get_ssl_data(c); + if (c == NULL) { + /* stream already closed */ + return; + } + } +#endif + if (c->idle) { return; } @@ -2191,6 +2253,22 @@ ngx_http_upstream_send_request(ngx_http_ if (!u->request_body_sent) { u->request_body_sent = 1; +#if (NGX_HTTP_V3) + + /* + * need to finalize QUIC stream, to notify that no more data expected + * otherwise, server expecting more data (although C-L is present!) + */ + + if (c->quic && !u->hq) { + if (ngx_quic_shutdown_stream(c, NGX_WRITE_SHUTDOWN) != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } +#endif + if (u->header_sent) { return; } @@ -2329,6 +2407,9 @@ static void ngx_http_upstream_send_request_handler(ngx_http_request_t *r, ngx_http_upstream_t *u) { +#if (NGX_HTTP_V3) + ngx_int_t rc; +#endif ngx_connection_t *c; c = u->peer.connection; @@ -2341,6 +2422,42 @@ ngx_http_upstream_send_request_handler(n return; } +#if (NGX_HTTP_V3) + + if (u->h3) { + if (!u->h3_started) { + + rc = ngx_http_v3_upstream_connected(r, u, c); + + if (rc == NGX_DECLINED) { + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); + return; + } + + if (rc != NGX_OK) { + ngx_http_upstream_finalize_request(r, u, + NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + } else { + + if (u->header_sent && !u->conf->preserve_output) { + u->write_event_handler = ngx_http_upstream_dummy_handler; + + (void) ngx_handle_write_event(c->write, 0); + + return; + } + + ngx_http_upstream_send_request(r, u, 1); + } + + return; + } + +#endif + #if (NGX_HTTP_SSL) if (u->ssl && c->ssl == NULL) { @@ -4475,14 +4592,58 @@ static void ngx_http_upstream_close_peer_connection(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_uint_t no_send) { - ngx_pool_t *pool; - ngx_connection_t *c; + ngx_pool_t *pool; + ngx_connection_t *c; +#if (NGX_HTTP_V3) + ngx_connection_t *sc; +#endif c = u->peer.connection; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "close http upstream connection: %d", c->fd); +#if (NGX_HTTP_V3) + if (c->type == SOCK_DGRAM || c->quic) { + + if (c->quic) { + /* a quic stream */ + + sc = c; + c = c->quic->parent; + + ngx_http_v3_upstream_close_request_stream(sc, 1); + + if (u->h3_started && !u->hq) { + /* HTTP/3 was initialized on this stream, close gracefully */ + ngx_http_v3_shutdown(c); + } + } + + if (c->udp) { + /* main QUIC udp connection */ + ngx_quic_finalize_connection(c, 0 /* NGX_QUIC_ERR_NO_ERROR */, ""); + + } else { + /* + * early error, we failed to create quic connection object, + * cleanup normal connection created by upstream + */ + pool = c->pool; + + ngx_close_connection(c); + + if (pool) { + ngx_destroy_pool(pool); + } + } + + u->peer.connection = NULL; + + return; + } +#endif + #if (NGX_HTTP_SSL) if (c->ssl) { c->ssl->no_wait_shutdown = 1; @@ -6522,6 +6683,397 @@ ngx_http_upstream_bind_set_slot(ngx_conf } +#if (NGX_HTTP_V3) + +static ngx_int_t +ngx_http_v3_upstream_init_connection(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_connection_t *c) +{ + ngx_int_t rc; + ngx_connection_t *sc; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 upstream init connection on c:%p", c); + + c->sockaddr = u->peer.sockaddr; + c->socklen = u->peer.socklen; + + c->addr_text.data = ngx_pnalloc(c->pool, u->peer.name->len); + if (c->addr_text.data == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(c->addr_text.data, u->peer.name->data, u->peer.name->len); + + if (ngx_quic_create_client(&u->conf->quic, c) != NGX_OK) { + return NGX_ERROR; + } + + c->listening->handler = ngx_http_v3_init_client_stream; + + r->connection->log->action = "QUIC handshaking to upstream"; + + if (ngx_http_v3_upstream_init_h3(c, r) != NGX_OK) { + return NGX_ERROR; + } + + sc = ngx_quic_open_stream(c, 1); + if (sc == NULL) { + return NGX_ERROR; + } + + sc->data = r; + + /* + * main quic connection lives own life, we will acess it via stream + * when required + */ + u->peer.connection = sc; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->log, 0, + "http3 client bidi stream created sc:%p", sc); + + rc = ngx_quic_connect(c, ngx_http_v3_upstream_init_ssl, sc); + + if (rc == NGX_AGAIN) { + ngx_add_timer(sc->write, u->conf->connect_timeout); + sc->write->handler = ngx_http_v3_upstream_connect_handler; + sc->read->handler = ngx_http_quic_stream_close_handler; + + return NGX_OK; + } + + if (rc != NGX_OK) { + return NGX_ERROR; + } + + return ngx_http_v3_upstream_connected(r, u, sc); +} + + +static ngx_int_t +ngx_http_v3_upstream_init_ssl(ngx_connection_t *c, void *data) +{ + ngx_connection_t *sc = data; + + ngx_str_t *alpn; + ngx_http_request_t *r; + ngx_http_upstream_t *u; + + if (sc == NULL) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "http3 stream cannot be reused"); + return NGX_ERROR; + } + + r = sc->data; + u = r->upstream; + + /* u->peer.connection (quic stream) shares SSL object with main conn */ + sc->ssl = c->ssl; + + alpn = &u->conf->quic.alpn; + + if (SSL_set_alpn_protos(c->ssl->connection, (uint8_t *) alpn->data, + alpn->len) + != 0) + { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "http3 SSL_set_alpn_protos() failed"); + return NGX_ERROR; + } + + if (u->conf->ssl_server_name || u->conf->ssl_verify) { + if (ngx_http_upstream_ssl_name(r, u, c) != NGX_OK) { + return NGX_ERROR; + } + } + + 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) { + return NGX_ERROR; + } + } + + if (u->conf->ssl_session_reuse) { + c->ssl->save_session = ngx_http_upstream_ssl_save_session; + + if (u->peer.set_session(&u->peer, u->peer.data) != NGX_OK) { + return NGX_ERROR; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_upstream_reuse_connection(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_connection_t *c) +{ + ngx_connection_t *sc; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 upstream reuse connection c:%p", c); + + if (ngx_http_upstream_configure(r, u, c) != NGX_OK) { + return NGX_ERROR; + } + + sc = ngx_quic_open_stream(c, 1); + if (sc == NULL) { + return NGX_ERROR; + } + + sc->data = r; + u->peer.connection = sc; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, sc->log, 0, + "http3 client bidi stream created sc:%p", sc); + + return ngx_http_v3_upstream_send_request(r, u, sc); +} + + +static ngx_int_t +ngx_http_v3_upstream_init_h3(ngx_connection_t *c, ngx_http_request_t *r) +{ + ngx_http_v3_session_t *h3c; + ngx_http_log_ctx_t *ctx; + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); + if (hc == NULL) { + return NGX_ERROR; + } + + hc->ssl = 1; + + c->data = hc; + + /* hc->addr_conf is unused */ + hc->conf_ctx = cscf->ctx; /* needed for streams to get config */ + + ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + return NGX_ERROR; + } + + ctx->connection = c; + ctx->request = NULL; + ctx->current_request = NULL; + + c->log_error = NGX_ERROR_INFO; + + if (ngx_http_v3_init_session(c) != NGX_OK) { + return NGX_ERROR; + } + + h3c = ngx_http_v3_get_session(c); + + h3c->client = 1; + + return NGX_OK; +} + + +static void +ngx_http_v3_upstream_connect_handler(ngx_event_t *ev) +{ + ngx_uint_t ft_type; + ngx_connection_t *c, *sc; + ngx_http_request_t *r; + ngx_http_upstream_t *u; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, "http3 connect handler"); + + sc = ev->data; + r = sc->data; + c = r->connection; + u = r->upstream; + + if (ev->timedout) { + ft_type = NGX_HTTP_UPSTREAM_FT_TIMEOUT; + ngx_connection_error(c, NGX_ETIMEDOUT, "http3 connection timed out"); + goto next; + } + + if (ev->error) { + ngx_connection_error(c, 0, "http3 connection error"); + ft_type = NGX_HTTP_UPSTREAM_FT_ERROR; + goto next; + } + + if (ev->closed) { + ngx_connection_error(c, 0, "http3 connection was closed"); + ft_type = NGX_HTTP_UPSTREAM_FT_ERROR; + goto next; + } + + ngx_http_set_log_request(c->log, r); + + if (ngx_http_v3_upstream_connected(r, u, sc) != NGX_OK) { + ft_type = NGX_HTTP_UPSTREAM_FT_ERROR; + goto next; + } + + ngx_http_run_posted_requests(c); + + return; + +next: + + ngx_http_upstream_next(r, u, ft_type); +} + + +static ngx_int_t +ngx_http_v3_upstream_connected(ngx_http_request_t *r, ngx_http_upstream_t *u, + ngx_connection_t *sc) +{ + ngx_int_t rc; + ngx_connection_t *c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, sc->log, 0, "http3 upstream connected"); + + if (sc->write->timer_set) { + /* remove connection timeout timer */ + ngx_del_timer(sc->write); + } + + sc->write->handler = ngx_http_quic_upstream_dummy_handler; + + c = sc->quic->parent; + + if (u->conf->ssl_verify) { + + rc = SSL_get_verify_result(c->ssl->connection); + + if (rc != X509_V_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "upstream SSL certificate verify error: (%l:%s)", + rc, X509_verify_cert_error_string(rc)); + + return NGX_DECLINED; + } + + if (ngx_ssl_check_host(c, &u->ssl_name) != NGX_OK) { + ngx_log_error(NGX_LOG_ERR, c->log, 0, + "upstream SSL certificate does not match \"%V\"", + &u->ssl_name); + return NGX_DECLINED; + } + } + + if (!u->hq) { + if (ngx_http_v3_send_settings(c) != NGX_OK) { + /* example error: qc->closing is set */ + return NGX_ERROR; + } + } + + if (ngx_http_v3_upstream_send_request(r, u, sc) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_upstream_send_request(ngx_http_request_t *r, + ngx_http_upstream_t *u, ngx_connection_t *sc) +{ + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, sc->log, 0, + "http3 upstream send request: \"%V?%V\"", &r->uri, &r->args); + + sc->sendfile = 0; + u->output.sendfile = 0; + + sc->write->handler = ngx_http_upstream_handler; + sc->read->handler = ngx_http_upstream_handler; + + u->writer.connection = sc; + u->h3_started = 1; + + ngx_http_upstream_send_request(r, u, 1); + + return NGX_OK; +} + + +void +ngx_http_v3_upstream_close_request_stream(ngx_connection_t *c, + ngx_uint_t do_reset) +{ + ngx_pool_t *pool; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 upstream close request stream"); + + if (do_reset) { + ngx_http_v3_reset_stream(c); + } + + c->destroyed = 1; + + pool = c->pool; + + ngx_quic_client_set_ssl_data(c->quic->parent, NULL); + + /* will remove any c->read/write timers */ + ngx_close_connection(c); + + /* will trigger quic stream cleanup handler */ + ngx_destroy_pool(pool); +} + + +static void +ngx_http_quic_upstream_dummy_handler(ngx_event_t *ev) +{ +} + + +static void +ngx_http_quic_stream_close_handler(ngx_event_t *ev) +{ + ngx_connection_t *c, *sc; + ngx_http_request_t *r; + ngx_http_upstream_t *u; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, ev->log, 0, + "http quic stream close handler"); + + sc = ev->data; + + if (sc->close) { + r = sc->data; + u = r->upstream; + c = r->connection; + + /* + * main quic connection is closing due to some error; + * continue next upstream process normally; stream will + * be closed by ngx_http_upstream_close_peer_connection() + */ + + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR); + + ngx_http_run_posted_requests(c); + } +} + +#endif + + static ngx_int_t ngx_http_upstream_set_local(ngx_http_request_t *r, ngx_http_upstream_t *u, ngx_http_upstream_local_t *local) diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. */ @@ -242,6 +243,10 @@ typedef struct { ngx_str_t module; +#if (NGX_HTTP_V3) + ngx_quic_conf_t quic; +#endif + NGX_COMPAT_BEGIN(2) NGX_COMPAT_END } ngx_http_upstream_conf_t; @@ -399,6 +404,11 @@ struct ngx_http_upstream_s { unsigned request_body_sent:1; unsigned request_body_blocked:1; unsigned header_sent:1; +#if (NGX_HTTP_V3) + unsigned h3:1; + unsigned h3_started:1; + unsigned hq:1; +#endif }; @@ -429,6 +439,10 @@ ngx_int_t ngx_http_upstream_hide_headers ngx_http_upstream_conf_t *conf, ngx_http_upstream_conf_t *prev, ngx_str_t *default_hide_headers, ngx_hash_init_t *hash); +#if (NGX_HTTP_V3) +void ngx_http_v3_upstream_close_request_stream(ngx_connection_t *c, + ngx_uint_t do_reset); +#endif #define ngx_http_conf_upstream_srv_conf(uscf, module) \ uscf->srv_conf[module.ctx_index] diff --git a/src/http/v3/ngx_http_v3.h b/src/http/v3/ngx_http_v3.h --- a/src/http/v3/ngx_http_v3.h +++ b/src/http/v3/ngx_http_v3.h @@ -80,7 +80,12 @@ #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_DELETE 16 #define NGX_HTTP_V3_HEADER_METHOD_GET 17 +#define NGX_HTTP_V3_HEADER_METHOD_HEAD 18 +#define NGX_HTTP_V3_HEADER_METHOD_OPTIONS 19 +#define NGX_HTTP_V3_HEADER_METHOD_POST 20 +#define NGX_HTTP_V3_HEADER_METHOD_PUT 21 #define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22 #define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23 #define NGX_HTTP_V3_HEADER_STATUS_200 25 @@ -158,11 +163,13 @@ struct ngx_http_v3_session_s { unsigned goaway:1; unsigned hq:1; + unsigned client:1; ngx_connection_t *known_streams[NGX_HTTP_V3_MAX_KNOWN_STREAM]; }; +void ngx_http_v3_init_client_stream(ngx_connection_t *c); 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); diff --git a/src/http/v3/ngx_http_v3_parse.c b/src/http/v3/ngx_http_v3_parse.c --- a/src/http/v3/ngx_http_v3_parse.c +++ b/src/http/v3/ngx_http_v3_parse.c @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Roman Arutyunyan * Copyright (C) Nginx, Inc. */ @@ -353,10 +354,12 @@ ngx_http_v3_parse_headers(ngx_connection case sw_verify: - rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count); - if (rc != NGX_OK) { - return rc; - } + if (c->quic != NULL) { + rc = ngx_http_v3_check_insert_count(c, st->prefix.insert_count); + if (rc != NGX_OK) { + return rc; + } + } /* else: check skipped for cached response */ st->state = sw_field_rep; @@ -392,9 +395,12 @@ 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; - } + + if (c->quic) { + if (ngx_http_v3_send_ack_section(c, c->quic->id) != NGX_OK) { + return NGX_ERROR; + } + } /* skip for cached response */ ngx_http_v3_ack_insert_count(c, st->prefix.insert_count); } @@ -614,13 +620,15 @@ ngx_http_v3_parse_literal(ngx_connection 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 (c->quic != NULL) { + 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; + } + } /* else: check skipped for cached response */ if (st->huffman) { n = n * 8 / 5; diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Roman Arutyunyan * Copyright (C) Nginx, Inc. */ @@ -56,6 +57,28 @@ static const struct { void +ngx_http_v3_init_client_stream(ngx_connection_t *c) +{ + ngx_http_connection_t *hc, *phc; + + phc = ngx_http_quic_get_connection(c); + + hc = ngx_pcalloc(c->pool, sizeof(ngx_http_connection_t)); + if (hc == NULL) { + ngx_http_close_connection(c); + return; + } + + c->data = hc; + + /* server configuration used by 'client' streams */ + hc->conf_ctx = phc->conf_ctx; + + ngx_http_v3_init_stream(c); +} + + +void ngx_http_v3_init_stream(ngx_connection_t *c) { ngx_http_connection_t *hc, *phc; diff --git a/src/http/v3/ngx_http_v3_uni.c b/src/http/v3/ngx_http_v3_uni.c --- a/src/http/v3/ngx_http_v3_uni.c +++ b/src/http/v3/ngx_http_v3_uni.c @@ -1,5 +1,6 @@ /* + * Copyright (C) 2023 Web Server LLC * Copyright (C) Roman Arutyunyan * Copyright (C) Nginx, Inc. */ @@ -107,33 +108,44 @@ ngx_int_t ngx_http_v3_register_uni_stream(ngx_connection_t *c, uint64_t type) { ngx_int_t index; + ngx_uint_t encoder, decoder, control; ngx_http_v3_session_t *h3c; ngx_http_v3_uni_stream_t *us; h3c = ngx_http_v3_get_session(c); + if (h3c->client) { + encoder = NGX_HTTP_V3_STREAM_SERVER_ENCODER; + decoder = NGX_HTTP_V3_STREAM_SERVER_DECODER; + control = NGX_HTTP_V3_STREAM_SERVER_CONTROL; + + } else { + encoder = NGX_HTTP_V3_STREAM_CLIENT_ENCODER; + decoder = NGX_HTTP_V3_STREAM_CLIENT_DECODER; + control = NGX_HTTP_V3_STREAM_CLIENT_CONTROL; + } + 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; + index = 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; + index = 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; - + index = control; break; default: @@ -141,9 +153,9 @@ ngx_http_v3_register_uni_stream(ngx_conn 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) + if (h3c->known_streams[encoder] == NULL + || h3c->known_streams[decoder] == NULL + || h3c->known_streams[control] == NULL) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "missing mandatory stream"); return NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR; @@ -317,21 +329,25 @@ ngx_http_v3_get_uni_stream(ngx_connectio 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: - index = NGX_HTTP_V3_STREAM_SERVER_ENCODER; + index = h3c->client ? NGX_HTTP_V3_STREAM_CLIENT_ENCODER + : NGX_HTTP_V3_STREAM_SERVER_ENCODER; break; case NGX_HTTP_V3_STREAM_DECODER: - index = NGX_HTTP_V3_STREAM_SERVER_DECODER; + index = h3c->client ? NGX_HTTP_V3_STREAM_CLIENT_DECODER + : NGX_HTTP_V3_STREAM_SERVER_DECODER; break; case NGX_HTTP_V3_STREAM_CONTROL: - index = NGX_HTTP_V3_STREAM_SERVER_CONTROL; + index = h3c->client ? NGX_HTTP_V3_STREAM_CLIENT_CONTROL + : 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]) { @@ -380,10 +396,13 @@ ngx_http_v3_get_uni_stream(ngx_connectio failed: - ngx_log_error(NGX_LOG_ERR, c->log, 0, "failed to create server stream"); + ngx_log_error(NGX_LOG_ERR, c->log, 0, + h3c->client ? "failed to create client stream" + : "failed to create server stream"); ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_STREAM_CREATION_ERROR, - "failed to create server stream"); + h3c->client ? "failed to create client stream" + : "failed to create server stream"); if (sc) { ngx_http_v3_close_uni_stream(sc); }
_______________________________________________ nginx-devel mailing list nginx-devel@nginx.org https://mailman.nginx.org/mailman/listinfo/nginx-devel