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 <[email protected]>
# 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
[email protected]
https://mailman.nginx.org/mailman/listinfo/nginx-devel