From 91d229fb2f1adac032ceeeebf305a9f0e7467623 Mon Sep 17 00:00:00 2001 From: kern Date: Mon, 22 Jul 2024 15:44:58 +0800 Subject: [PATCH 1/4] add http2 proxy --- auto/modules | 2 + src/core/njt_connection.h | 4 + src/core/njt_output_chain.c | 1 + src/http/modules/njt_http_proxy_module.c | 1137 ++++++++++++++++- src/http/modules/njt_http_proxy_module.h | 7 + .../njt_http_upstream_keepalive_module.c | 44 +- src/http/njt_http_core_module.c | 1 + src/http/njt_http_header_filter_module.c | 48 + src/http/njt_http_request.h | 1 + src/http/njt_http_request_body.c | 20 +- src/http/njt_http_upstream.c | 563 ++++++-- src/http/njt_http_upstream.h | 28 + src/http/v2/njt_http_v2.c | 699 ++++++++-- src/http/v2/njt_http_v2.h | 36 + src/http/v2/njt_http_v2_filter_module.c | 65 +- src/http/v2/njt_http_v2_stream.c | 885 +++++++++++++ src/http/v2/njt_http_v2_stream.h | 28 + src/http/v2/njt_http_v2_table.c | 4 +- 18 files changed, 3312 insertions(+), 261 deletions(-) create mode 100644 src/http/v2/njt_http_v2_stream.c create mode 100644 src/http/v2/njt_http_v2_stream.h diff --git a/auto/modules b/auto/modules index be6fc9c3..213aafff 100755 --- a/auto/modules +++ b/auto/modules @@ -432,10 +432,12 @@ if [ $HTTP = YES ]; then njt_module_name=njt_http_v2_module njt_module_incs=src/http/v2 njt_module_deps="src/http/v2/njt_http_v2.h \ + src/http/v2/njt_http_v2_stream.h \ src/http/v2/njt_http_v2_module.h" njt_module_srcs="src/http/v2/njt_http_v2.c \ src/http/v2/njt_http_v2_table.c \ src/http/v2/njt_http_v2_encode.c \ + src/http/v2/njt_http_v2_stream.c \ src/http/v2/njt_http_v2_module.c" njt_module_libs= njt_module_link=$HTTP_V2 diff --git a/src/core/njt_connection.h b/src/core/njt_connection.h index 1f6a4620..b0881ea2 100644 --- a/src/core/njt_connection.h +++ b/src/core/njt_connection.h @@ -165,6 +165,10 @@ struct njt_connection_s { njt_ssl_connection_t *ssl; #endif +#if (NJT_HTTP_V2) + void *stream; +#endif + njt_udp_connection_t *udp; struct sockaddr *local_sockaddr; diff --git a/src/core/njt_output_chain.c b/src/core/njt_output_chain.c index 2943aace..200eff12 100644 --- a/src/core/njt_output_chain.c +++ b/src/core/njt_output_chain.c @@ -506,6 +506,7 @@ njt_output_chain_copy_buf(njt_output_chain_ctx_t *ctx) src = ctx->in->buf; dst = ctx->buf; + dst->last_buf = 0; size = njt_buf_size(src); size = njt_min(size, dst->end - dst->pos); diff --git a/src/http/modules/njt_http_proxy_module.c b/src/http/modules/njt_http_proxy_module.c index 86e6009f..16eab0f7 100644 --- a/src/http/modules/njt_http_proxy_module.c +++ b/src/http/modules/njt_http_proxy_module.c @@ -3,6 +3,7 @@ * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2024 JD Technology Information Technology Co., Ltd. */ @@ -133,7 +134,54 @@ static char * njt_http_proxy_ssl_alpn(njt_conf_t *cf, njt_command_t *cmd, void *conf); #endif - +#if (NJT_HTTP_V2) + +/* context for creating http/2 request */ +typedef struct { + /* calculated length of request */ + size_t n; + + /* encode method state */ + njt_str_t method; + + /* encode path state */ + size_t loc_len; + size_t uri_len; + uintptr_t escape; + njt_uint_t unparsed_uri; + + /* tmp buff */ + u_char *tmp; + size_t max_tmp_len; + + /* encode headers state */ + size_t max_head; + njt_http_proxy_headers_t *headers; + njt_http_script_engine_t le; + njt_http_script_engine_t e; + +} njt_http_v2_proxy_ctx_t; + +static njt_int_t njt_http_v2_proxy_create_request(njt_http_request_t *r); +static njt_int_t njt_http_v2_proxy_encode_method(njt_http_request_t *r, + njt_http_v2_proxy_ctx_t *v2c, njt_buf_t *b); +static njt_inline njt_uint_t njt_http_v2_map_method(njt_uint_t method); +static njt_int_t njt_http_v2_proxy_encode_path(njt_http_request_t *r, + njt_http_v2_proxy_ctx_t *v2c, njt_buf_t *b); +static njt_int_t njt_http_v2_proxy_encode_authority(njt_http_request_t *r, + njt_http_v2_proxy_ctx_t *v2c, njt_buf_t *b); +static njt_int_t njt_http_v2_proxy_encode_headers(njt_http_request_t *r, + njt_http_v2_proxy_ctx_t *v2c, njt_buf_t *b); +static njt_int_t njt_http_v2_proxy_body_length(njt_http_request_t *r, + njt_http_v2_proxy_ctx_t *v2c); +static njt_chain_t *njt_http_v2_proxy_encode_body(njt_http_request_t *r, + njt_http_v2_proxy_ctx_t *v2c); +static njt_int_t njt_http_v2_proxy_reinit_request(njt_http_request_t *r); +static njt_int_t njt_http_v2_proxy_process_header(njt_http_request_t *r); +static void njt_http_v2_proxy_abort_request(njt_http_request_t *r); +static void njt_http_v2_proxy_finalize_request(njt_http_request_t *r, + njt_int_t rc); +#endif static njt_conf_post_t njt_http_proxy_lowat_post = @@ -179,6 +227,8 @@ static njt_conf_post_t njt_http_proxy_ssl_conf_command_post = static njt_conf_enum_t njt_http_proxy_http_version[] = { { njt_string("1.0"), NJT_HTTP_VERSION_10 }, { njt_string("1.1"), NJT_HTTP_VERSION_11 }, + { njt_string("2"), NJT_HTTP_VERSION_20 }, + { njt_string("3"), NJT_HTTP_VERSION_30 }, { njt_null_string, 0 } }; @@ -571,7 +621,8 @@ static njt_command_t njt_http_proxy_commands[] = { &njt_http_upstream_ignore_headers_masks }, { njt_string("proxy_http_version"), - NJT_HTTP_MAIN_CONF|NJT_HTTP_SRV_CONF|NJT_HTTP_LOC_CONF|NJT_CONF_TAKE1, + NJT_HTTP_MAIN_CONF|NJT_HTTP_SRV_CONF|NJT_HTTP_LOC_CONF + |NJT_HTTP_LIF_CONF|NJT_HTTP_LMT_CONF|NJT_CONF_TAKE1, njt_conf_set_enum_slot, NJT_HTTP_LOC_CONF_OFFSET, offsetof(njt_http_proxy_loc_conf_t, http_version), @@ -708,7 +759,32 @@ static njt_command_t njt_http_proxy_commands[] = { #endif #endif +#if (NJT_HTTP_V2) + { njt_string("http2_max_concurrent_streams"), + NJT_HTTP_MAIN_CONF|NJT_HTTP_SRV_CONF|NJT_HTTP_LOC_CONF|NJT_CONF_TAKE1, + njt_conf_set_num_slot, + NJT_HTTP_LOC_CONF_OFFSET, + offsetof(njt_http_proxy_loc_conf_t, + upstream.h2_conf.concurrent_streams), + NULL }, + + { njt_string("http2_streams_index_size"), + NJT_HTTP_MAIN_CONF|NJT_HTTP_SRV_CONF|NJT_HTTP_LOC_CONF|NJT_CONF_TAKE1, + njt_conf_set_num_slot, + NJT_HTTP_LOC_CONF_OFFSET, + offsetof(njt_http_proxy_loc_conf_t, + upstream.h2_conf.streams_index_mask), + NULL }, + + { njt_string("http2_streams_recv_window"), + NJT_HTTP_MAIN_CONF|NJT_HTTP_SRV_CONF|NJT_HTTP_LOC_CONF|NJT_CONF_TAKE1, + njt_conf_set_num_slot, + NJT_HTTP_LOC_CONF_OFFSET, + offsetof(njt_http_proxy_loc_conf_t, + upstream.h2_conf.recv_window), + NULL }, +#endif njt_null_command }; @@ -760,6 +836,54 @@ static njt_keyval_t njt_http_proxy_headers[] = { { njt_null_string, njt_null_string } }; +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + +/* + * 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 njt_keyval_t njt_http_v3_proxy_headers[] = { + { njt_string("Content-Length"), njt_string("$proxy_internal_body_length") }, +#if 0 + /* TODO: trailers */ + { njt_string("TE"), njt_string("$v3_proxy_internal_trailers") }, +#endif + { njt_string("Host"), njt_string("") }, + { njt_string("Connection"), njt_string("") }, + { njt_string("Transfer-Encoding"), njt_string("") }, + { njt_string("Keep-Alive"), njt_string("") }, + { njt_string("Expect"), njt_string("") }, + { njt_string("Upgrade"), njt_string("") }, + { njt_null_string, njt_null_string } +}; + +#if (NJT_HTTP_CACHE) + +static njt_keyval_t njt_http_v3_proxy_cache_headers[] = { + { njt_string("Host"), njt_string("") }, + { njt_string("Connection"), njt_string("") }, + { njt_string("Content-Length"), njt_string("$proxy_internal_body_length") }, + { njt_string("Transfer-Encoding"), njt_string("") }, + { njt_string("TE"), njt_string("") }, + { njt_string("Keep-Alive"), njt_string("") }, + { njt_string("Expect"), njt_string("") }, + { njt_string("Upgrade"), njt_string("") }, + { njt_string("If-Modified-Since"), + njt_string("$upstream_cache_last_modified") }, + { njt_string("If-Unmodified-Since"), njt_string("") }, + { njt_string("If-None-Match"), njt_string("$upstream_cache_etag") }, + { njt_string("If-Match"), njt_string("") }, + { njt_string("Range"), njt_string("") }, + { njt_string("If-Range"), njt_string("") }, + { njt_null_string, njt_null_string } +}; + +#endif + +#endif static njt_str_t njt_http_proxy_hide_headers[] = { njt_string("Date"), @@ -917,6 +1041,9 @@ njt_http_proxy_handler(njt_http_request_t *r) u = r->upstream; if (plcf->proxy_lengths == NULL) { +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + ctx->host = plcf->host; +#endif ctx->vars = plcf->vars; u->schema = plcf->vars.schema; #if(NJT_HTTP_DYN_PROXY_PASS) @@ -952,6 +1079,18 @@ njt_http_proxy_handler(njt_http_request_t *r) u->finalize_request = njt_http_proxy_finalize_request; r->state = 0; +#if (NJT_HTTP_V2) + if (plcf->http_version == NJT_HTTP_VERSION_20) { + u->h2 = 1; + + u->create_request = njt_http_v2_proxy_create_request; + u->reinit_request = njt_http_v2_proxy_reinit_request; + u->process_header = njt_http_v2_proxy_process_header; + u->abort_request = njt_http_v2_proxy_abort_request; + u->finalize_request = njt_http_v2_proxy_finalize_request; + } +#endif + if (plcf->redirects) { u->rewrite_redirect = njt_http_proxy_rewrite_redirect; } @@ -979,7 +1118,14 @@ njt_http_proxy_handler(njt_http_request_t *r) if (!plcf->upstream.request_buffering && plcf->body_values == NULL && plcf->upstream.pass_request_body && (!r->headers_in.chunked - || plcf->http_version == NJT_HTTP_VERSION_11)) + || (plcf->http_version == NJT_HTTP_VERSION_11 +#if (NJT_HTTP_V3) + || plcf->http_version == NJT_HTTP_VERSION_30 +#endif +#if (NJT_HTTP_V2) + || plcf->http_version == NJT_HTTP_VERSION_20 +#endif + ))) { r->request_body_no_buffering = 1; } @@ -1096,7 +1242,21 @@ njt_http_proxy_eval(njt_http_request_t *r, njt_http_proxy_ctx_t *ctx, u->resolved->host = url.host; u->resolved->port = (in_port_t) (url.no_port ? port : url.port); u->resolved->no_port = url.no_port; +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + if (url.family != AF_UNIX) { + + if (url.no_port) { + ctx->host = url.host; + + } else { + ctx->host.len = url.host.len + 1 + url.port_text.len; + ctx->host.data = url.host.data; + } + } else { + njt_str_set(&ctx->host, "localhost"); + } +#endif return NJT_OK; } @@ -3528,6 +3688,11 @@ njt_http_proxy_create_loc_conf(njt_conf_t *cf) njt_str_set(&conf->upstream.module, "proxy"); +#if (NJT_HTTP_V2) + conf->upstream.h2_conf.recv_window = NJT_CONF_UNSET_SIZE; + conf->upstream.h2_conf.concurrent_streams = NJT_CONF_UNSET_UINT; + conf->upstream.h2_conf.streams_index_mask = NJT_CONF_UNSET_UINT; +#endif return conf; } @@ -3541,6 +3706,7 @@ njt_http_proxy_merge_loc_conf(njt_conf_t *cf, void *parent, void *child) u_char *p; size_t size; njt_int_t rc; + njt_keyval_t *proxy_headers; njt_hash_init_t hash; njt_http_core_loc_conf_t *clcf; njt_http_proxy_rewrite_t *pr; @@ -3959,6 +4125,14 @@ njt_http_proxy_merge_loc_conf(njt_conf_t *cf, void *parent, void *child) hash.bucket_size = conf->headers_hash_bucket_size; hash.name = "proxy_headers_hash"; +#if (NJT_HTTP_V2 ) + if (conf->http_version == NJT_HTTP_VERSION_20) { + conf->upstream.alpn.data = (unsigned char *) + NJT_HTTP_V2_ALPN_PROTO; + conf->upstream.alpn.len = sizeof(NJT_HTTP_V2_ALPN_PROTO) - 1; + + } +#endif if (njt_http_upstream_hide_headers_hash(cf, &conf->upstream, &prev->upstream, njt_http_proxy_hide_headers, &hash) != NJT_OK) @@ -3981,6 +4155,9 @@ njt_http_proxy_merge_loc_conf(njt_conf_t *cf, void *parent, void *child) conf->location = prev->location; conf->vars = prev->vars; +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + conf->host = prev->host; +#endif conf->proxy_lengths = prev->proxy_lengths; conf->proxy_values = prev->proxy_values; @@ -4021,15 +4198,33 @@ njt_http_proxy_merge_loc_conf(njt_conf_t *cf, void *parent, void *child) njt_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 (NJT_HTTP_V3 || NJT_HTTP_V2) + /* H3 uses own set of headers, so do not inherit on version change */ + && !((conf->http_version >= NJT_HTTP_VERSION_20 + || prev->http_version >= NJT_HTTP_VERSION_20) + && conf->http_version != prev->http_version) + #endif + ) + { conf->headers = prev->headers; #if (NJT_HTTP_CACHE) conf->headers_cache = prev->headers_cache; +#endif +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + conf->host_set = prev->host_set; #endif } + proxy_headers = njt_http_proxy_headers; +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + if (conf->http_version == NJT_HTTP_VERSION_20 || + conf->http_version == NJT_HTTP_VERSION_30) { + proxy_headers = njt_http_v3_proxy_headers; + } +#endif rc = njt_http_proxy_init_headers(cf, conf, &conf->headers, - njt_http_proxy_headers); + proxy_headers); if (rc != NJT_OK) { return NJT_CONF_ERROR; } @@ -4037,8 +4232,17 @@ njt_http_proxy_merge_loc_conf(njt_conf_t *cf, void *parent, void *child) #if (NJT_HTTP_CACHE) if (conf->upstream.cache) { + + proxy_headers = njt_http_proxy_cache_headers; + +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + if (conf->http_version == NJT_HTTP_VERSION_20 || + conf->http_version == NJT_HTTP_VERSION_30) { + proxy_headers = njt_http_v3_proxy_cache_headers; + } +#endif rc = njt_http_proxy_init_headers(cf, conf, &conf->headers_cache, - njt_http_proxy_cache_headers); + proxy_headers); if (rc != NJT_OK) { return NJT_CONF_ERROR; } @@ -4057,9 +4261,19 @@ njt_http_proxy_merge_loc_conf(njt_conf_t *cf, void *parent, void *child) prev->headers = conf->headers; #if (NJT_HTTP_CACHE) prev->headers_cache = conf->headers_cache; +#endif +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + prev->host_set = conf->host_set; #endif } - +#if (NJT_HTTP_V2 ) + njt_conf_merge_size_value(conf->upstream.h2_conf.recv_window, + prev->upstream.h2_conf.recv_window, 65536); + njt_conf_merge_uint_value(conf->upstream.h2_conf.concurrent_streams, + prev->upstream.h2_conf.concurrent_streams, 128); + njt_conf_merge_uint_value(conf->upstream.h2_conf.streams_index_mask, + prev->upstream.h2_conf.streams_index_mask, 32 - 1); +#endif return NJT_CONF_OK; } @@ -4110,6 +4324,13 @@ njt_http_proxy_init_headers(njt_conf_t *cf, njt_http_proxy_loc_conf_t *conf, src = conf->headers_source->elts; for (i = 0; i < conf->headers_source->nelts; i++) { +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + if (src[i].key.len == 4 + && njt_strncasecmp(src[i].key.data, (u_char *) "Host", 4) == 0) + { + conf->host_set = 1; + } +#endif s = njt_array_push(&headers_merged); if (s == NULL) { return NJT_ERROR; @@ -4324,6 +4545,22 @@ njt_http_proxy_pass(njt_conf_t *cf, njt_command_t *cmd, void *conf) plcf->vars.schema.data = url->data; plcf->vars.key_start = plcf->vars.schema; +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + if (u.family != AF_UNIX) { + + if (u.no_port) { + plcf->host = u.host; + + } else { + plcf->host.len = u.host.len + 1 + u.port_text.len; + plcf->host.data = u.host.data; + } + + } else { + njt_str_set(&plcf->host, "localhost"); + } +#endif + njt_http_proxy_set_vars(&u, &plcf->vars); plcf->location = clcf->name; @@ -5415,5 +5652,891 @@ njt_http_proxy_ssl_alpn(njt_conf_t *cf, njt_command_t *cmd, void *conf) } #endif +#if (NJT_HTTP_V2) + +static njt_int_t +njt_http_v2_proxy_create_request(njt_http_request_t *r) +{ + njt_buf_t *b; + njt_chain_t *cl ,*out, *body; + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + njt_http_v2_proxy_ctx_t v2c; + njt_http_proxy_headers_t *headers; + njt_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[] + */ + + njt_log_debug0(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2 proxy create request"); + u = r->upstream; + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + +#if (NJT_HTTP_CACHE) + headers = u->cacheable ? &plcf->headers_cache : &plcf->headers; +#else + headers = &plcf->headers; +#endif + njt_memzero(&v2c,sizeof(njt_http_v2_proxy_ctx_t)); + + njt_http_script_flush_no_cacheable_variables(r, plcf->body_flushes); + njt_http_script_flush_no_cacheable_variables(r, headers->flushes); + + v2c.headers = headers; + /* calculate lengths */ + + njt_http_v2_proxy_encode_method(r, &v2c, NULL); + + //schme + v2c.n += 1; + + if (njt_http_v2_proxy_encode_path(r, &v2c, NULL) != NJT_OK) { + return NJT_ERROR; + } + + if (njt_http_v2_proxy_encode_authority(r, &v2c, NULL) != NJT_OK) { + return NJT_ERROR; + } + + if (njt_http_v2_proxy_body_length(r, &v2c) != NJT_OK) { + return NJT_ERROR; + } + + if (njt_http_v2_proxy_encode_headers(r, &v2c, NULL) != NJT_OK) { + return NJT_ERROR; + } + + /* generate HTTP/2 request of known size */ + + b = njt_create_temp_buf(r->pool, v2c.n); + if (b == NULL) { + return NJT_ERROR; + } + + if (v2c.max_tmp_len > 0) { + v2c.tmp = njt_pnalloc(r->pool,v2c.max_tmp_len); + if (v2c.tmp == NULL) { + return NJT_ERROR; + } + } + + if (njt_http_v2_proxy_encode_method(r, &v2c, b) != NJT_OK) { + return NJT_ERROR; + } + + *b->last++ = njt_http_v2_indexed(NJT_HTTP_V2_SCHEME_HTTPS_INDEX); + + + if (njt_http_v2_proxy_encode_path(r, &v2c, b) != NJT_OK) { + return NJT_ERROR; + } + + if (njt_http_v2_proxy_encode_authority(r, &v2c, b) != NJT_OK) { + return NJT_ERROR; + } + + if (njt_http_v2_proxy_encode_headers(r, &v2c, b) != NJT_OK) { + return NJT_ERROR; + } + + cl = njt_alloc_chain_link(r->pool); + if (cl == NULL) { + return NJT_ERROR; + } + + if (r->stream) { + b->last_buf = r->stream->in_closed || + (r->headers_in.content_length_n <= 0 && !r->headers_in.chunked); + } else { + //当下游为http3时,r->headers_in.content_length_n == 0和 + //r->headers_in.chunked ==1 同时成立 + b->last_buf = r->headers_in.content_length_n == 0 || + (r->headers_in.content_length_n < 0 && !r->headers_in.chunked); + } + + cl->buf = b; + cl->next = NULL; + out = cl; + + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + + if (r->request_body_no_buffering || ctx->internal_chunked) { + //http2 在fake client层封包,不需要特殊处理,缺省即可 + u->output.output_filter = njt_chain_writer; + u->output.filter_ctx = &u->writer; + + } else if (ctx->internal_body_length != -1) { + + body = njt_http_v2_proxy_encode_body(r, &v2c); + if (body == NJT_CHAIN_ERROR) { + return NJT_ERROR; + } + + for (cl = out; cl->next; cl = cl->next) { } + cl->next = body; + } + + /* TODO: trailers */ + u->request_bufs = out; + + return NJT_OK; +} + +static njt_int_t +njt_http_v2_proxy_encode_method(njt_http_request_t *r, + njt_http_v2_proxy_ctx_t *v2c, njt_buf_t *b) +{ + size_t n; + njt_str_t method; + njt_uint_t v3method; + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + njt_http_proxy_loc_conf_t *plcf; + + static njt_str_t njt_http_v2_header_method = njt_string(":method"); + + if (b == NULL) { + /* calculate length */ + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + ctx = njt_http_get_module_ctx(r, njt_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 (njt_http_complex_value(r, plcf->method, &method) != NJT_OK) { + return NJT_ERROR; + } + } else { + method = r->method_name; + } + + if (method.len == 4 + && njt_strncasecmp(method.data, (u_char *) "HEAD", 4) == 0) + { + ctx->head = 1; + } + + if (method.len) { + n = 1 + NJT_HTTP_V2_INT_OCTETS + njt_http_v2_header_method.len + + NJT_HTTP_V2_INT_OCTETS + method.len; + } else { + + v3method = njt_http_v2_map_method(r->method); + + if (v3method) { + n = 1; + + } else { + n = 1 + NJT_HTTP_V2_INT_OCTETS + njt_http_v2_header_method.len + + NJT_HTTP_V2_INT_OCTETS + r->method_name.len; + } + } + if (n > v2c->max_tmp_len) { + v2c->max_tmp_len = n; + } + v2c->n += n; + v2c->method = method; + + return NJT_OK; + } + + method = v2c->method; + + if (method.len) { + *b->last++ = 0; + b->last = njt_http_v2_write_name(b->last,njt_http_v2_header_method.data, + njt_http_v2_header_method.len,v2c->tmp); + b->last = njt_http_v2_write_value(b->last,method.data,method.len,v2c->tmp); + } else { + + v3method = njt_http_v2_map_method(r->method); + + if (v3method) { + *b->last++ = njt_http_v2_indexed(v3method); + } else { + *b->last++ = 0; + b->last = njt_http_v2_write_name(b->last,njt_http_v2_header_method.data, + njt_http_v2_header_method.len,v2c->tmp); + b->last = njt_http_v2_write_value(b->last,r->method_name.data,r->method_name.len,v2c->tmp); + } + } + + return NJT_OK; +} + +static njt_inline njt_uint_t +njt_http_v2_map_method(njt_uint_t method) +{ + switch (method) { + case NJT_HTTP_GET: + return NJT_HTTP_V2_METHOD_GET_INDEX; + case NJT_HTTP_POST: + return NJT_HTTP_V2_METHOD_POST_INDEX; + default: + return 0; + } +} + +static njt_int_t +njt_http_v2_proxy_encode_headers(njt_http_request_t *r, + njt_http_v2_proxy_ctx_t *v2c, njt_buf_t *b) +{ + u_char *p, *start; + size_t key_len, val_len, hlen, max_head, n; + njt_str_t tmp, tmpv; + njt_uint_t i; + njt_list_part_t *part; + njt_table_elt_t *header; + njt_http_script_code_pt code; + njt_http_proxy_headers_t *headers; + njt_http_script_engine_t *le; + njt_http_script_engine_t *e; + njt_http_proxy_loc_conf_t *plcf; + njt_http_script_len_code_pt lcode; + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + + headers = v2c->headers; + le = &v2c->le; + e = &v2c->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 = *(njt_http_script_len_code_pt *) le->ip; + key_len = lcode(le); + + for (val_len = 0; *(uintptr_t *) le->ip; val_len += lcode(le)) { + lcode = *(njt_http_script_len_code_pt *) le->ip; + } + le->ip += sizeof(uintptr_t); + + if (val_len == 0) { + continue; + } + + hlen = key_len + val_len; + if (hlen > max_head) { + max_head = hlen; + } + if (key_len > v2c->max_tmp_len) { + v2c->max_tmp_len = key_len; + } + + if (val_len > v2c->max_tmp_len) { + v2c->max_tmp_len = val_len; + } + + n += 1 + NJT_HTTP_V2_INT_OCTETS + key_len + + NJT_HTTP_V2_INT_OCTETS + val_len; + } + + 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 (njt_hash_find(&headers->hash, header[i].hash, + header[i].lowcase_key, header[i].key.len)) + { + continue; + } + n += 1 + NJT_HTTP_V2_INT_OCTETS + header[i].key.len + + NJT_HTTP_V2_INT_OCTETS + header[i].value.len; + + if (header[i].key.len > v2c->max_tmp_len) { + v2c->max_tmp_len = header[i].key.len; + } + if (header[i].value.len > v2c->max_tmp_len) { + v2c->max_tmp_len = header[i].value.len; + } + } + } + + v2c->n += n; + v2c->max_head = max_head; + + return NJT_OK; + } + + max_head = v2c->max_head; + + p = njt_pnalloc(r->pool, max_head); + if (p == NULL) { + return NJT_ERROR; + } + + start = p; + + njt_memzero(e, sizeof(njt_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 = *(njt_http_script_len_code_pt *) le->ip; + (void) lcode(le); + + for (val_len = 0; *(uintptr_t *) le->ip; val_len += lcode(le)) { + lcode = *(njt_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 = *(njt_http_script_code_pt *) e->ip; + code((njt_http_script_engine_t *) e); + } + e->ip += sizeof(uintptr_t); + + e->skip = 0; + + continue; + } + + code = *(njt_http_script_code_pt *) e->ip; + code((njt_http_script_engine_t *) e); + + tmp.len = e->pos - tmp.data; + tmpv.data = e->pos; + + while (*(uintptr_t *) e->ip) { + code = *(njt_http_script_code_pt *) e->ip; + code((njt_http_script_engine_t *) e); + } + e->ip += sizeof(uintptr_t); + + tmpv.len = e->pos - tmpv.data; + + *b->last++ = 0; + b->last = njt_http_v2_write_name(b->last,tmp.data,tmp.len,v2c->tmp); + b->last = njt_http_v2_write_value(b->last,tmpv.data,tmpv.len,v2c->tmp); + + 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 (njt_hash_find(&headers->hash, header[i].hash, + header[i].lowcase_key, header[i].key.len)) + { + continue; + } + + *b->last++ = 0; + b->last = njt_http_v2_write_name(b->last,header[i].key.data,header[i].key.len,v2c->tmp); + b->last = njt_http_v2_write_value(b->last,header[i].value.data,header[i].value.len,v2c->tmp); + + njt_log_debug2(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \"%V: %V\"", + &header[i].key, &header[i].value); + } + } + + return NJT_OK; +} + +static njt_int_t +njt_http_v2_proxy_encode_path(njt_http_request_t *r, + njt_http_v2_proxy_ctx_t *v2c, njt_buf_t *b) +{ + size_t n; + u_char *p; + size_t loc_len; + size_t uri_len; + uintptr_t escape; + njt_uint_t unparsed_uri; + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + njt_http_proxy_loc_conf_t *plcf; + + static njt_str_t njt_http_v2_path = njt_string(":path"); + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + ctx = njt_http_get_module_ctx(r, njt_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 * njt_escape_uri(NULL, r->uri.data + loc_len, + r->uri.len - loc_len, + NJT_ESCAPE_URI); + } + + uri_len = ctx->vars.uri.len + r->uri.len - loc_len + escape + + sizeof("?") - 1 + r->args.len; + } + + if (uri_len == 0) { + njt_log_error(NJT_LOG_ERR, r->connection->log, 0, + "zero length URI to proxy"); + return NJT_ERROR; + } + + n = 1 + NJT_HTTP_V2_INT_OCTETS + njt_http_v2_path.len + + NJT_HTTP_V2_INT_OCTETS + uri_len; + + v2c->n += n; + + if (uri_len > v2c->max_tmp_len) { + v2c->max_tmp_len = uri_len; + } + + v2c->escape = escape; + v2c->uri_len = uri_len; + v2c->loc_len = loc_len; + v2c->unparsed_uri = unparsed_uri; + + return NJT_OK; + } + + u = r->upstream; + + escape = v2c->escape; + uri_len = v2c->uri_len; + loc_len = v2c->loc_len; + unparsed_uri = v2c->unparsed_uri; + + p = njt_palloc(r->pool, uri_len); + if (p == NULL) { + return NJT_ERROR; + } + + u->uri.data = p; + + if (plcf->proxy_lengths && ctx->vars.uri.len) { + p = njt_copy(p, ctx->vars.uri.data, ctx->vars.uri.len); + + } else if (unparsed_uri) { + p = njt_copy(p, r->unparsed_uri.data, r->unparsed_uri.len); + + } else { + if (r->valid_location) { + p = njt_copy(p, ctx->vars.uri.data, ctx->vars.uri.len); + } + + if (escape) { + njt_escape_uri(p, r->uri.data + loc_len, + r->uri.len - loc_len, NJT_ESCAPE_URI); + p += r->uri.len - loc_len + escape; + + } else { + p = njt_copy(p, r->uri.data + loc_len, r->uri.len - loc_len); + } + + if (r->args.len > 0) { + *p++ = '?'; + p = njt_copy(p, r->args.data, r->args.len); + } + } + + u->uri.len = p - u->uri.data; + *b->last++ = 0; + b->last = njt_http_v2_write_name(b->last,njt_http_v2_path.data,njt_http_v2_path.len,v2c->tmp); + b->last = njt_http_v2_write_value(b->last,u->uri.data,u->uri.len,v2c->tmp); + return NJT_OK; +} + +static njt_int_t +njt_http_v2_proxy_encode_authority(njt_http_request_t *r, + njt_http_v2_proxy_ctx_t *v2c, njt_buf_t *b) +{ + size_t n; + njt_http_proxy_ctx_t *ctx; + njt_http_proxy_loc_conf_t *plcf; + static njt_str_t njt_http_v2_auth = njt_string(":authority"); + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + + if (plcf->host_set) { + return NJT_OK; + } + + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + + if (b == NULL) { + + n = 1 + NJT_HTTP_V2_INT_OCTETS + njt_http_v2_auth.len + + NJT_HTTP_V2_INT_OCTETS + ctx->host.len; + v2c->n += n; + if ( ctx->host.len > v2c->max_tmp_len) { + v2c->max_tmp_len = ctx->host.len; + } + + return NJT_OK; + } + *b->last++ = 0; + b->last = njt_http_v2_write_name(b->last,njt_http_v2_auth.data,njt_http_v2_auth.len,v2c->tmp); + b->last = njt_http_v2_write_value(b->last,ctx->host.data,ctx->host.len,v2c->tmp); + + njt_log_debug1(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2 header: \":authority: %V\"", &ctx->host); + + return NJT_OK; +} + +static njt_int_t +njt_http_v2_proxy_body_length(njt_http_request_t *r, + njt_http_v2_proxy_ctx_t *v2c) +{ + size_t body_len; + njt_http_proxy_ctx_t *ctx; + njt_http_script_engine_t *le; + njt_http_proxy_loc_conf_t *plcf; + njt_http_script_len_code_pt lcode; + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + + le = &v2c->le; + + 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 = *(njt_http_script_len_code_pt *) le->ip; + body_len += lcode(le); + } + + ctx->internal_body_length = 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; + } + + return NJT_OK; +} + +static njt_chain_t * +njt_http_v2_proxy_encode_body(njt_http_request_t *r, + njt_http_v2_proxy_ctx_t *v2c) +{ + njt_buf_t *b; + njt_chain_t *cl, *body, *prev, *head; + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + njt_http_script_code_pt code; + njt_http_script_engine_t *e; + njt_http_proxy_loc_conf_t *plcf; + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + /* body set in configuration */ + + u = r->upstream; + + if (plcf->body_values) { + + e = &v2c->e; + + cl = njt_alloc_chain_link(r->pool); + if (cl == NULL) { + return NJT_CHAIN_ERROR; + } + + b = njt_create_temp_buf(r->pool, ctx->internal_body_length); + if (b == NULL) { + return NJT_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 = *(njt_http_script_code_pt *) e->ip; + code((njt_http_script_engine_t *) e); + } + + b->last = e->pos; + b->last_buf = 1; + + 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 = njt_alloc_buf(r->pool); + if (b == NULL) { + return NJT_CHAIN_ERROR; + } + + njt_memcpy(b, body->buf, sizeof(njt_buf_t)); + + cl = njt_alloc_chain_link(r->pool); + if (cl == NULL) { + return NJT_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 njt_int_t +njt_http_v2_proxy_process_header(njt_http_request_t *r) +{ + u_char *p; + njt_buf_t *b; + njt_int_t rc; + njt_connection_t *c, c_stub; + njt_http_upstream_t *u; + njt_http_v2_connection_t *h2c, h2c_stub; + njt_http_v2_stream_t *stream,stream_stub; + njt_http_v2_state_t tmp_state; + njt_http_core_srv_conf_t *cscf; + + u = r->upstream; + c = u->peer.connection; + + njt_log_debug3(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2 proxy header cache:%p, c:%p, buffer:%d", + r->cache, c, u->buffer.last - u->buffer.pos); + +#if (NJT_HTTP_CACHE) + /*from cache file*/ + if (r->cache && + ( !c + || u->cache_status == NJT_HTTP_CACHE_STALE + || u->cache_status == NJT_HTTP_CACHE_REVALIDATED )) + { + njt_memzero(&h2c_stub,sizeof(njt_http_v2_connection_t)); + njt_memzero(&stream_stub,sizeof(njt_http_v2_stream_t)); + njt_memzero(&c_stub,sizeof(njt_connection_t)); + njt_memzero(&tmp_state,sizeof(njt_http_v2_state_t)); + + cscf = njt_http_get_module_srv_conf(r, njt_http_core_module); + + h2c = &h2c_stub; + h2c->fake = 1; + h2c->client = 1; + h2c->pool = r->pool; + h2c->state.pool = r->pool; + h2c->state.flags |= NJT_HTTP_V2_END_HEADERS_FLAG; + h2c->state.handler = njt_http_v2_state_header_block; + h2c->state.length = r->cache->body_start - r->cache->header_start; + h2c->state.keep_pool = 1; + h2c->http_connection = r->http_connection; + h2c->state.header_limit = cscf->large_client_header_buffers.size + * cscf->large_client_header_buffers.num; + c = &c_stub; + c->log = r->connection->log; + c->pool = r->pool; + h2c->connection = c; + + stream = &stream_stub; + stream->state = &tmp_state; + stream->request = r; + h2c->state.stream = stream; + } else { +#endif + stream = c->stream; + h2c = njt_http_v2_get_connection(c); +#if (NJT_HTTP_CACHE) + } +#endif + /*save state*/ + njt_memcpy(&tmp_state, &h2c->state, sizeof(njt_http_v2_state_t)); + njt_memcpy(&h2c->state, stream->state, sizeof(njt_http_v2_state_t)); + + b = &u->buffer; + p = b->pos; + + h2c->state.parse = 1; + rc = njt_http_v2_parse_headers(h2c, b); + h2c->state.parse = 0; + + /*restore state*/ + njt_memcpy(stream->state, &h2c->state, sizeof(njt_http_v2_state_t)); + njt_memcpy(&h2c->state, &tmp_state, sizeof(njt_http_v2_state_t)); + + njt_log_debug3(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "njt_http_v2_parse_headers rc:%d pos:%p, last:%p", + rc, b->pos, b->last); + + if (rc == NJT_ERROR) { + return NJT_ERROR; + } + + if (h2c) { + h2c->total_bytes += b->pos - p; + } + + if (rc == NJT_AGAIN) { + return NJT_AGAIN; + } + + return NJT_OK; +} + +static njt_int_t +njt_http_v2_proxy_reinit_request(njt_http_request_t *r) +{ + njt_http_proxy_ctx_t *ctx; + + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + + if (ctx == NULL) { + return NJT_OK; + } + + ctx->status.code = 0; + ctx->status.count = 0; + ctx->status.start = NULL; + ctx->status.end = NULL; + ctx->chunked.state = 0; + + r->upstream->process_header = njt_http_v2_proxy_process_header; + r->upstream->pipe->input_filter = njt_http_proxy_copy_filter; + r->upstream->input_filter = njt_http_proxy_non_buffered_copy_filter; + r->state = 0; + + return NJT_OK; +} + +static void +njt_http_v2_proxy_abort_request(njt_http_request_t *r) +{ + njt_log_debug0(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "abort http v2 proxy request"); +} + + +static void +njt_http_v2_proxy_finalize_request(njt_http_request_t *r, njt_int_t rc) +{ + njt_log_debug0(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "finalize http v2 proxy request"); +} + +#endif diff --git a/src/http/modules/njt_http_proxy_module.h b/src/http/modules/njt_http_proxy_module.h index febc663e..c18949aa 100644 --- a/src/http/modules/njt_http_proxy_module.h +++ b/src/http/modules/njt_http_proxy_module.h @@ -130,6 +130,10 @@ typedef struct { njt_str_t ori_url; #endif +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + njt_str_t host; + njt_uint_t host_set; +#endif } njt_http_proxy_loc_conf_t; @@ -142,6 +146,9 @@ typedef struct { njt_chain_t *free; njt_chain_t *busy; +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + njt_str_t host; +#endif unsigned head:1; unsigned internal_chunked:1; unsigned header_sent:1; diff --git a/src/http/modules/njt_http_upstream_keepalive_module.c b/src/http/modules/njt_http_upstream_keepalive_module.c index 1964baf9..bdbb8c49 100644 --- a/src/http/modules/njt_http_upstream_keepalive_module.c +++ b/src/http/modules/njt_http_upstream_keepalive_module.c @@ -283,6 +283,17 @@ found: njt_log_debug1(NJT_LOG_DEBUG_HTTP, pc->log, 0, "get keepalive peer: using connection %p", c); +#if (NJT_HTTP_V2) + if (c->stream) { + njt_http_v2_connection_t *h2c = ((njt_http_v2_stream_t*)c->stream)->connection; + pc->connection = h2c->connection; + pc->cached = 1; + h2c->processing++; + njt_http_v2_close_stream(c->stream,0); + h2c->processing--; + return NJT_DONE; + } +#endif c->idle = 0; c->sent = 0; c->data = NULL; @@ -309,6 +320,7 @@ njt_http_upstream_free_keepalive_peer(njt_peer_connection_t *pc, void *data, njt_http_upstream_keepalive_peer_data_t *kp = data; njt_http_upstream_keepalive_cache_t *item; + njt_uint_t requests; njt_queue_t *q; njt_connection_t *c; njt_http_upstream_t *u; @@ -321,18 +333,40 @@ njt_http_upstream_free_keepalive_peer(njt_peer_connection_t *pc, void *data, u = kp->upstream; c = pc->connection; + if (c == NULL) { + goto invalid; + } + if (state & NJT_PEER_FAILED || c == NULL +#if (NJT_HTTP_V3 || NJT_HTTP_V2) + /* quic stream is done when using: ok to have EOF/write err */ + || (c->quic == NULL && c->stream == NULL && c->read->eof) + || (c->quic == NULL && c->stream == 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 (NJT_HTTP_V3) + requests = c->quic ? c->quic->parent->requests : c->requests; +#else + requests = c->requests; +#endif +#if (NJT_HTTP_V2) + if (c->stream) { + njt_http_v2_connection_t *h2c = ((njt_http_v2_stream_t*)c->stream)->connection; + requests = h2c->connection->requests; + } +#endif - if (c->requests >= kp->conf->requests) { + if (requests >= kp->conf->requests) { goto invalid; } @@ -466,6 +500,12 @@ static void njt_http_upstream_keepalive_close(njt_connection_t *c) { +#if (NJT_HTTP_V2) + if (c->stream) { + njt_http_v2_close_stream(c->stream,0); + return; + } +#endif #if (NJT_HTTP_SSL) if (c->ssl) { diff --git a/src/http/njt_http_core_module.c b/src/http/njt_http_core_module.c index 6203d1c7..4f2a211d 100644 --- a/src/http/njt_http_core_module.c +++ b/src/http/njt_http_core_module.c @@ -2595,6 +2595,7 @@ njt_http_subrequest(njt_http_request_t *r, #if (NJT_HTTP_V2) sr->stream = r->stream; + sr->http_connection = r->http_connection; #endif sr->method = NJT_HTTP_GET; diff --git a/src/http/njt_http_header_filter_module.c b/src/http/njt_http_header_filter_module.c index eec0d015..24ba45b6 100644 --- a/src/http/njt_http_header_filter_module.c +++ b/src/http/njt_http_header_filter_module.c @@ -625,6 +625,54 @@ njt_http_header_filter(njt_http_request_t *r) return njt_http_write_filter(r, &out); } +njt_str_t * +njt_http_status_line(njt_uint_t status) +{ + njt_str_t *status_line; + + if (status >= NJT_HTTP_OK + && status < NJT_HTTP_LAST_2XX) + { + /* 2XX */ + + status -= NJT_HTTP_OK; + status_line = &njt_http_status_lines[status]; + + } else if (status >= NJT_HTTP_MOVED_PERMANENTLY + && status < NJT_HTTP_LAST_3XX) + { + /* 3XX */ + + status = status - NJT_HTTP_MOVED_PERMANENTLY + + NJT_HTTP_OFF_3XX; + + status_line = &njt_http_status_lines[status]; + + } else if (status >= NJT_HTTP_BAD_REQUEST + && status < NJT_HTTP_LAST_4XX) + { + /* 4XX */ + status = status - NJT_HTTP_BAD_REQUEST + + NJT_HTTP_OFF_4XX; + + status_line = &njt_http_status_lines[status]; + + } else if (status >= NJT_HTTP_INTERNAL_SERVER_ERROR + && status < NJT_HTTP_LAST_5XX) + { + /* 5XX */ + status = status - NJT_HTTP_INTERNAL_SERVER_ERROR + + NJT_HTTP_OFF_5XX; + + status_line = &njt_http_status_lines[status]; + + } else { + return NULL; + } + + return status_line; +} + static njt_int_t njt_http_header_filter_init(njt_conf_t *cf) diff --git a/src/http/njt_http_request.h b/src/http/njt_http_request.h index c4e4e8ab..901129dc 100644 --- a/src/http/njt_http_request.h +++ b/src/http/njt_http_request.h @@ -636,6 +636,7 @@ typedef struct { #define njt_http_ephemeral(r) (void *) (&r->uri_start) +njt_str_t *njt_http_status_line(njt_uint_t status); extern njt_http_header_t njt_http_headers_in[]; extern njt_http_header_out_t njt_http_headers_out[]; diff --git a/src/http/njt_http_request_body.c b/src/http/njt_http_request_body.c index 54279fe4..7a02639c 100644 --- a/src/http/njt_http_request_body.c +++ b/src/http/njt_http_request_body.c @@ -641,6 +641,11 @@ njt_http_discard_request_body(njt_http_request_t *r) #if (NJT_HTTP_V2) if (r->stream) { r->stream->skip_data = 1; + //forbid RST_STEAM + r->stream->in_closed = 1; + /*这种情况下,接收包头,未接收完包体,即向后端转发请求, + 后端响应请求后,即关闭stream,导致RST_STEAM发送, + 进而导致request失败,需优化HTTP2废弃包处理*/ return NJT_OK; } #endif @@ -1222,6 +1227,13 @@ njt_http_request_body_chunked_filter(njt_http_request_t *r, njt_chain_t *in) rb->rest = 0; + if (out) { + for (cl = out; cl->next; cl = cl->next) { } + cl->buf->last_buf = 1; + break; + } + /*增加空buf,njt_chain_update_chains方法会回收, + 设置last_buf = 1 标志不生效 */ tl = njt_chain_get_free_buf(r->pool, &rb->free); if (tl == NULL) { return NJT_HTTP_INTERNAL_SERVER_ERROR; @@ -1298,14 +1310,15 @@ njt_http_request_body_save_filter(njt_http_request_t *r, njt_chain_t *in) for (cl = in; cl; cl = cl->next) { - njt_log_debug7(NJT_LOG_DEBUG_EVENT, r->connection->log, 0, + njt_log_debug8(NJT_LOG_DEBUG_EVENT, r->connection->log, 0, "http body new buf t:%d f:%d %p, pos %p, size: %z " - "file: %O, size: %O", + "file: %O, size: %O last:%d", cl->buf->temporary, cl->buf->in_file, cl->buf->start, cl->buf->pos, cl->buf->last - cl->buf->pos, cl->buf->file_pos, - cl->buf->file_last - cl->buf->file_pos); + cl->buf->file_last - cl->buf->file_pos, + cl->buf->last_buf); if (cl->buf->last_buf) { @@ -1377,6 +1390,7 @@ njt_http_request_body_save_filter(njt_http_request_t *r, njt_chain_t *in) b->in_file = 1; b->file_last = rb->temp_file->file.offset; b->file = &rb->temp_file->file; + b->last_buf = 1; rb->bufs = cl; } diff --git a/src/http/njt_http_upstream.c b/src/http/njt_http_upstream.c index 6309abd3..245c975e 100644 --- a/src/http/njt_http_upstream.c +++ b/src/http/njt_http_upstream.c @@ -3,6 +3,7 @@ * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2024 JD Technology Information Technology Co., Ltd. */ @@ -45,6 +46,8 @@ static void njt_http_upstream_check_broken_connection(njt_http_request_t *r, // static void njt_http_upstream_connect(njt_http_request_t *r, // njt_http_upstream_t *u); //end update by clb +static njt_int_t njt_http_upstream_configure(njt_http_request_t *r, + njt_http_upstream_t *u, njt_connection_t *c); static njt_int_t njt_http_upstream_reinit(njt_http_request_t *r, njt_http_upstream_t *u); static void njt_http_upstream_send_request(njt_http_request_t *r, @@ -103,6 +106,8 @@ static void njt_http_upstream_dummy_handler(njt_http_request_t *r, njt_http_upstream_t *u); static void njt_http_upstream_next(njt_http_request_t *r, njt_http_upstream_t *u, njt_uint_t ft_type); +static void njt_http_upstream_close_peer_connection(njt_http_request_t *r, + njt_http_upstream_t *u, njt_uint_t no_send); static void njt_http_upstream_cleanup(void *data); static void njt_http_upstream_finalize_request(njt_http_request_t *r, njt_http_upstream_t *u, njt_int_t rc); @@ -185,7 +190,7 @@ static void *njt_http_upstream_create_main_conf(njt_conf_t *cf); static char *njt_http_upstream_init_main_conf(njt_conf_t *cf, void *conf); #if (NJT_HTTP_SSL) -static void njt_http_upstream_ssl_init_connection(njt_http_request_t *, +static void njt_http_upstream_ssl_init_connection(njt_http_request_t *r, njt_http_upstream_t *u, njt_connection_t *c); static void njt_http_upstream_ssl_handshake_handler(njt_connection_t *c); static void njt_http_upstream_ssl_handshake(njt_http_request_t *, @@ -201,7 +206,20 @@ static njt_int_t njt_http_upstream_ssl_certificates(njt_http_request_t *r, #endif #endif +#if (NJT_HTTP_V2) +static njt_int_t njt_http_v2_upstream_init_connection(njt_http_request_t *, + njt_http_upstream_t *u, njt_connection_t *c); +static njt_int_t njt_http_v2_upstream_init(njt_connection_t *c); +static njt_int_t njt_http_v2_upstream_reuse_connection(njt_http_request_t *r, + njt_http_upstream_t *u, njt_connection_t *c); +static njt_int_t njt_http_v2_upstream_send_header(njt_http_request_t *r); +njt_http_v2_out_frame_t *njt_http_v2_create_headers_frame(njt_http_v2_stream_t *stream, + u_char *pos, u_char *end, njt_uint_t fin); +njt_int_t njt_http_v2_filter_send(njt_connection_t *fc, + njt_http_v2_stream_t *stream); +void njt_http_v2_filter_cleanup(void *data); +#endif static njt_http_upstream_header_t njt_http_upstream_headers_in[] = { @@ -1142,7 +1160,8 @@ njt_http_upstream_cache_send(njt_http_request_t *r, njt_http_upstream_t *u) r->cached = 1; c = r->cache; - + njt_log_debug0(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "http upstream cache send reponse"); if (c->header_start == c->body_start) { r->http_version = NJT_HTTP_VERSION_9; return njt_http_cache_send(r); @@ -1619,8 +1638,6 @@ njt_http_upstream_connect(njt_http_request_t *r, njt_http_upstream_t *u) { njt_int_t rc; njt_connection_t *c; - njt_http_core_loc_conf_t *clcf; - r->connection->log->action = "connecting to upstream"; @@ -1677,22 +1694,24 @@ njt_http_upstream_connect(njt_http_request_t *r, njt_http_upstream_t *u) c = u->peer.connection; c->requests++; +#if (NJT_HTTP_V2) + if (u->h2) { + c->idle = 1; + if (u->peer.cached ) { + if (njt_http_v2_upstream_reuse_connection(r, u, c) != NJT_OK) { + njt_http_upstream_finalize_request(r, u, + NJT_HTTP_INTERNAL_SERVER_ERROR); + } + return; + } + } +#endif c->data = r; c->write->handler = njt_http_upstream_handler; c->read->handler = njt_http_upstream_handler; - u->write_event_handler = njt_http_upstream_send_request_handler; - u->read_event_handler = njt_http_upstream_process_header; - - c->sendfile &= r->connection->sendfile; - u->output.sendfile = c->sendfile; - - if (r->connection->tcp_nopush == NJT_TCP_NOPUSH_DISABLED) { - c->tcp_nopush = NJT_TCP_NOPUSH_DISABLED; - } - if (c->pool == NULL) { /* we need separate pool here to be able to cache SSL connections */ @@ -1710,6 +1729,67 @@ njt_http_upstream_connect(njt_http_request_t *r, njt_http_upstream_t *u) c->read->log = c->log; c->write->log = c->log; + c->sendfile &= r->connection->sendfile; + + if (r->connection->tcp_nopush == NJT_TCP_NOPUSH_DISABLED) { + c->tcp_nopush = NJT_TCP_NOPUSH_DISABLED; + } + + if (njt_http_upstream_configure(r, u, c) != NJT_OK) { + njt_http_upstream_finalize_request(r, u, + NJT_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + if (rc == NJT_AGAIN) { + // njt_add_timer(c->write, u->conf->connect_timeout); openresty patch + njt_add_timer(c->write, u->connect_timeout); // openresty patch + return; + } + +#if (NJT_HTTP_SSL) + + if (u->ssl && c->ssl == NULL) { + njt_http_upstream_ssl_init_connection(r, u, c); + return; + } + +#endif + +#if (NJT_HTTP_V2) + if (u->h2) { + rc = njt_http_v2_upstream_init_connection(r, u, c); + + if (rc == NJT_DECLINED) { + njt_http_upstream_next(r, u, NJT_HTTP_UPSTREAM_FT_ERROR); + return; + } + + if (rc != NJT_OK) { + njt_http_upstream_finalize_request(r, u, + NJT_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + njt_http_v2_upstream_init(c); + return; + } +#endif + + njt_http_upstream_send_request(r, u, 1); +} + +static njt_int_t +njt_http_upstream_configure(njt_http_request_t *r, njt_http_upstream_t *u, + njt_connection_t *c) +{ + njt_http_core_loc_conf_t *clcf; + + u->write_event_handler = njt_http_upstream_send_request_handler; + u->read_event_handler = njt_http_upstream_process_header; + + u->output.sendfile = c->sendfile; + /* init or reinit the njt_output_chain() and njt_chain_writer() contexts */ clcf = njt_http_get_module_loc_conf(r, njt_http_core_module); @@ -1721,9 +1801,7 @@ njt_http_upstream_connect(njt_http_request_t *r, njt_http_upstream_t *u) if (u->request_sent) { if (njt_http_upstream_reinit(r, u) != NJT_OK) { - njt_http_upstream_finalize_request(r, u, - NJT_HTTP_INTERNAL_SERVER_ERROR); - return; + return NJT_ERROR; } } @@ -1739,9 +1817,7 @@ njt_http_upstream_connect(njt_http_request_t *r, njt_http_upstream_t *u) u->output.free = njt_alloc_chain_link(r->pool); if (u->output.free == NULL) { - njt_http_upstream_finalize_request(r, u, - NJT_HTTP_INTERNAL_SERVER_ERROR); - return; + return NJT_ERROR; } u->output.free->buf = r->request_body->buf; @@ -1757,22 +1833,7 @@ njt_http_upstream_connect(njt_http_request_t *r, njt_http_upstream_t *u) u->request_body_sent = 0; u->request_body_blocked = 0; - if (rc == NJT_AGAIN) { - // njt_add_timer(c->write, u->conf->connect_timeout); openresty patch - njt_add_timer(c->write, u->connect_timeout); // openresty patch - return; - } - -#if (NJT_HTTP_SSL) - - if (u->ssl && c->ssl == NULL) { - njt_http_upstream_ssl_init_connection(r, u, c); - return; - } - -#endif - - njt_http_upstream_send_request(r, u, 1); + return NJT_OK; } @@ -1809,6 +1870,24 @@ njt_http_upstream_ssl_init_connection(njt_http_request_t *r, return; } +#if (NJT_HTTP_V2) + if (u->h2) { + njt_str_t * alpn = &u->conf->alpn; + + if (SSL_set_alpn_protos(c->ssl->connection, (uint8_t *) alpn->data, + alpn->len) + != 0) + { + njt_log_error(NJT_LOG_INFO, c->log, 0, + "http2 SSL_set_alpn_protos() failed"); + njt_http_upstream_finalize_request(r, u, + NJT_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + } +#endif + if (u->conf->ssl_server_name || u->conf->ssl_verify) { if (njt_http_upstream_ssl_name(r, u, c) != NJT_OK) { njt_http_upstream_finalize_request(r, u, @@ -1936,6 +2015,21 @@ njt_http_upstream_ssl_handshake(njt_http_request_t *r, njt_http_upstream_t *u, c->write->handler = njt_http_upstream_handler; c->read->handler = njt_http_upstream_handler; +#if (NJT_HTTP_V2) + if (u->h2 ) { + c->write->handler = njt_http_v2_write_handler; + c->read->handler = njt_http_v2_read_handler; + + rc = njt_http_v2_upstream_init_connection(r, u, c); + + if (rc != NJT_OK) { + goto failed; + } + njt_http_v2_upstream_init(c); + return; + } + +#endif njt_http_upstream_send_request(r, u, 1); return; @@ -1962,6 +2056,16 @@ njt_http_upstream_ssl_save_session(njt_connection_t *c) return; } +#if (NJT_HTTP_V2) + if (c->stream) { + njt_http_v2_connection_t *h2c = c->data; + c = h2c->init_ssl_data; + } +#endif + + if (c->idle) { + return; + } r = c->data; u = r->upstream; @@ -2394,8 +2498,18 @@ njt_http_upstream_send_request_body(njt_http_request_t *r, /* buffered request body */ if (!u->request_sent) { + +#if (NJT_HTTP_V2) + if (u->h2) { + if (njt_http_v2_upstream_send_header(r) != NJT_OK) { + return NJT_ERROR; + } + out = u->request_bufs->next; + } else { + out = u->request_bufs; + } +#endif u->request_sent = 1; - out = u->request_bufs; } else { out = NULL; @@ -2414,10 +2528,23 @@ njt_http_upstream_send_request_body(njt_http_request_t *r, } if (!u->request_sent) { + +#if (NJT_HTTP_V2) + if (u->h2) { + if (njt_http_v2_upstream_send_header(r) != NJT_OK) { + return NJT_ERROR; + } + out = u->request_bufs->next; + } else { + out = u->request_bufs; + } +#endif u->request_sent = 1; - out = u->request_bufs; - - if (r->request_body->bufs) { + if (r->request_body->bufs + #if (NJT_HTTP_V2) + && out + #endif + ) { for (cl = out; cl->next; cl = cl->next) { /* void */ } cl->next = r->request_body->bufs; r->request_body->bufs = NULL; @@ -2501,6 +2628,9 @@ njt_http_upstream_send_request_body(njt_http_request_t *r, njt_http_upstream_send_request_handler(njt_http_request_t *r, njt_http_upstream_t *u) { +#if (NJT_HTTP_V2 || NJT_HTTP_V3) + njt_int_t rc; +#endif njt_connection_t *c; c = u->peer.connection; @@ -2522,6 +2652,17 @@ njt_http_upstream_send_request_handler(njt_http_request_t *r, #endif +#if (NJT_HTTP_V2) + if (u->h2 && !u->h2_init) { + rc = njt_http_v2_upstream_init_connection(r, u, c); + + if (rc != NJT_OK) { + njt_http_upstream_finalize_request(r, u, + NJT_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } +#endif // if (u->header_sent && !u->conf->preserve_output) { // openresty patch if (u->request_body_sent && !u->conf->preserve_output) { // openresty patch u->write_event_handler = njt_http_upstream_dummy_handler; @@ -4641,28 +4782,54 @@ njt_http_upstream_next(njt_http_request_t *r, njt_http_upstream_t *u, } if (u->peer.connection) { - njt_log_debug1(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, - "close http upstream connection: %d", - u->peer.connection->fd); -#if (NJT_HTTP_SSL) + njt_http_upstream_close_peer_connection(r, u, 1); + + } - if (u->peer.connection->ssl) { - u->peer.connection->ssl->no_wait_shutdown = 1; - u->peer.connection->ssl->no_send_shutdown = 1; + njt_http_upstream_connect(r, u); +} - (void) njt_ssl_shutdown(u->peer.connection); +static void +njt_http_upstream_close_peer_connection(njt_http_request_t *r, + njt_http_upstream_t *u, njt_uint_t no_send) +{ + njt_pool_t *pool; + njt_connection_t *c; + + c = u->peer.connection; + + njt_log_debug1(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "close http upstream connection: %d", c->fd); + +#if (NJT_HTTP_V2) + if (u->h2 && u->h2_init) { + if (c->stream) { + njt_http_v2_close_stream(c->stream,0); + } else { + njt_http_v2_finalize_connection(c->data,0); } + return; + } #endif - if (u->peer.connection->pool) { - njt_destroy_pool(u->peer.connection->pool); +#if (NJT_HTTP_SSL) + if (c->ssl) { + c->ssl->no_wait_shutdown = 1; + c->ssl->no_send_shutdown = no_send; + + (void) njt_ssl_shutdown(c); } +#endif + + pool = c->pool; + + njt_close_connection(c); - njt_close_connection(u->peer.connection); - u->peer.connection = NULL; + if (pool) { + njt_destroy_pool(pool); } - njt_http_upstream_connect(r, u); + u->peer.connection = NULL; } @@ -4723,37 +4890,8 @@ njt_http_upstream_finalize_request(njt_http_request_t *r, } if (u->peer.connection) { - -#if (NJT_HTTP_SSL) - - /* TODO: do not shutdown persistent connection */ - - if (u->peer.connection->ssl) { - - /* - * We send the "close notify" shutdown alert to the upstream only - * and do not wait its "close notify" shutdown alert. - * It is acceptable according to the TLS standard. - */ - - u->peer.connection->ssl->no_wait_shutdown = 1; - - (void) njt_ssl_shutdown(u->peer.connection); - } -#endif - - njt_log_debug1(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, - "close http upstream connection: %d", - u->peer.connection->fd); - - if (u->peer.connection->pool) { - njt_destroy_pool(u->peer.connection->pool); - } - - njt_close_connection(u->peer.connection); - } - - u->peer.connection = NULL; + njt_http_upstream_close_peer_connection(r, u, 1); + } if (u->pipe && u->pipe->temp_file) { njt_log_debug1(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -7077,5 +7215,262 @@ njt_http_upstream_init_main_conf(njt_conf_t *cf, void *conf) return NJT_CONF_OK; } +#if (NJT_HTTP_V2) + +static njt_int_t njt_http_v2_upstream_init_connection(njt_http_request_t *r, + njt_http_upstream_t *u, njt_connection_t *c) { + njt_http_connection_t *hc; + njt_uint_t id; + njt_http_v2_node_t *node; + njt_connection_t *fc; + njt_http_v2_stream_t *stream; + njt_http_v2_connection_t *h2c; + njt_http_core_srv_conf_t *cscf; + + njt_log_debug0(NJT_LOG_DEBUG_HTTP, c->log, 0, "upstream init http2 connection"); + + c->log->action = "upstream HTTP/2 connection"; + + cscf = njt_http_get_module_srv_conf(r, njt_http_core_module); + + hc = njt_pcalloc(c->pool, sizeof(njt_http_connection_t)); + if (hc == NULL) { + return NJT_ERROR; + } + + hc->ssl = 1; + c->data = hc; + + /* hc->addr_conf is unused */ + hc->conf_ctx = cscf->ctx; /* needed for streams to get config */ + c->log_error = NJT_ERROR_INFO; + + if (njt_http_v2_create_client(&u->conf->h2_conf, c) != NJT_OK) { + return NJT_ERROR; + } + + h2c = c->data; + ++h2c->last_sid; + id = (h2c->last_sid << 1) -1; + node = njt_http_v2_get_node_by_id(h2c,id,1); + if (node == NULL) { + return NJT_ERROR; + } + node->weight = 100; + + stream = njt_http_v2_create_stream(h2c); + if (stream == NULL) { + return NJT_ERROR; + } + + njt_log_debug2(NJT_LOG_DEBUG_EVENT, c->log, 0, + "http2 upstream stream id:0x%xL create c:%p", id, c); + + fc = stream->fc; + fc->data = r; + fc->read->handler = njt_http_upstream_handler; + fc->write->handler = njt_http_upstream_handler; + + stream->request = r; + stream->connection = h2c; + stream->send_window = h2c->init_window; + stream->recv_window = u->conf->h2_conf.recv_window; + h2c->priority_limit += u->conf->h2_conf.concurrent_streams; + + h2c->state.pool = njt_create_pool(1024, h2c->connection->log); + if (h2c->state.pool == NULL) { + return NJT_ERROR; + } + stream->pool = h2c->state.pool; + stream->node = node; + + u->peer.connection = fc; + u->writer.connection = fc; + //u->stream = stream; + + + h2c->state.stream = stream; + h2c->state.keep_pool = 1; + h2c->init_ssl_data = fc; + + node->stream = stream; + + njt_http_v2_set_dependency(h2c, node, 1, 0); + u->h2_init = 1; + + return NJT_OK; +} +static njt_int_t +njt_http_v2_upstream_init(njt_connection_t *c) { + njt_uint_t i, size; + njt_http_v2_node_t *node; + njt_event_t *ev; + njt_http_v2_stream_t *stream; + njt_http_v2_connection_t *h2c; + njt_connection_t *fc; + njt_http_v2_srv_conf_t *h2scf; + + h2c = c->data; + + if (njt_http_v2_send_preface(h2c) != NJT_OK) { + return NJT_ERROR; + } + + if (njt_http_v2_send_settings(h2c) == NJT_ERROR) { + return NJT_ERROR; + } + + if (njt_http_v2_send_window_update(h2c, 0, NJT_HTTP_V2_MAX_WINDOW + - NJT_HTTP_V2_DEFAULT_WINDOW) + == NJT_ERROR) + { + return NJT_ERROR; + } + + h2scf = njt_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + njt_http_v2_module); + + size = njt_http_v2_index_size(h2scf); + + for (i = 0; i < size; i++) { + + for (node = h2c->streams_index[i]; node; node = node->index) { + stream = node->stream; + + if (stream == NULL) { + continue; + } + + fc = stream->fc; + ev = fc->write; + ev->handler(ev); + } + } + + if (njt_http_v2_send_output_queue(h2c) != NJT_OK) { + return NJT_ERROR; + } + return NJT_OK; +} + + +static njt_int_t +njt_http_v2_upstream_send_header(njt_http_request_t *r) { + njt_http_upstream_t *u; + njt_uint_t fin; + njt_http_v2_stream_t *stream; + njt_http_v2_out_frame_t *frame; + njt_http_v2_connection_t *h2c; + njt_connection_t *fc; + njt_http_cleanup_t *cln; + + u = r->upstream; + fc = u->peer.connection; + stream = fc->stream; + h2c = stream->connection; + + if (u->request_sent) { + return NJT_OK; + } + + fin = stream->in_closed || r->headers_in.content_length_n == 0 || + (r->headers_in.content_length_n < 0 && !r->headers_in.chunked); + njt_buf_t *out = u->request_bufs->buf; + + frame = njt_http_v2_create_headers_frame(stream,out->pos,out->last,fin); + if (frame == NULL) { + return NJT_ERROR; + } + + njt_http_v2_queue_blocked_frame(h2c, frame); + + stream->queued = 1; + + cln = njt_http_cleanup_add(r, 0); + if (cln == NULL) { + return NJT_ERROR; + } + + //cln->handler = njt_http_v2_filter_cleanup; + //cln->data = stream; + fc->need_last_buf = 1; + fc->need_flush_buf = 1; + //u->request_bufs = u->request_bufs->next; + return njt_http_v2_filter_send(fc,stream);; + +} + +static njt_int_t +njt_http_v2_upstream_reuse_connection(njt_http_request_t *r, + njt_http_upstream_t *u, njt_connection_t *c) { + njt_http_v2_node_t *node; + njt_connection_t *fc; + njt_http_v2_stream_t *stream; + njt_http_v2_connection_t *h2c; + njt_uint_t id; + + njt_log_debug1(NJT_LOG_DEBUG_HTTP, c->log, 0, + "http2 upstream reuse connection c:%p", c); + + if (njt_http_upstream_configure(r, u, c) != NJT_OK) { + return NJT_ERROR; + } + + h2c = c->data; + + stream = njt_http_v2_create_stream(h2c); + if (stream == NULL) { + return NJT_ERROR; + } + + fc = stream->fc; + fc->data = r; + fc->read->handler = njt_http_upstream_handler; + fc->write->handler = njt_http_upstream_handler; + + stream->request = r; + stream->connection = h2c; + stream->send_window = h2c->init_window; + stream->recv_window = u->conf->h2_conf.recv_window; + h2c->priority_limit += u->conf->h2_conf.concurrent_streams; + + h2c->state.pool = njt_create_pool(1024, h2c->connection->log); + if (h2c->state.pool == NULL) { + return NJT_ERROR; + } + stream->pool = h2c->state.pool; + + ++h2c->last_sid; + id = (h2c->last_sid << 1) -1; + node = njt_http_v2_get_node_by_id(h2c,id,1); + if (node == NULL) { + return NJT_ERROR; + } + node->weight = 100; + stream->node = node; + + njt_log_debug2(NJT_LOG_DEBUG_EVENT, c->log, 0, + "http2 upstream stream id:0x%xL create c:%p", id, c); + h2c->state.stream = stream; + h2c->state.keep_pool = 1; + h2c->init_ssl_data = fc; + + node->stream = stream; + + njt_http_v2_set_dependency(h2c, node, 1, 0); + + u->peer.connection = fc; + u->writer.connection = fc; + //u->stream = stream; + u->h2_init = 1; + + njt_log_debug1(NJT_LOG_DEBUG_HTTP, fc->log, 0, + "http2 client stream created fc:%p", fc); + + njt_http_upstream_send_request(r, u, 1); + return NJT_OK; +} + +#endif diff --git a/src/http/njt_http_upstream.h b/src/http/njt_http_upstream.h index 82996b54..73f8e139 100644 --- a/src/http/njt_http_upstream.h +++ b/src/http/njt_http_upstream.h @@ -172,6 +172,13 @@ typedef struct { #endif } njt_http_upstream_local_t; +#if (NJT_HTTP_V2) +typedef struct { + njt_uint_t concurrent_streams; + size_t recv_window; + njt_uint_t streams_index_mask; +} njt_http_v2_conf_t; +#endif typedef struct { njt_http_upstream_srv_conf_t *upstream; @@ -283,6 +290,18 @@ typedef struct { njt_str_t module; +#if (NJT_HTTP_V2 || NGX_HTTP_V3) + njt_str_t alpn; +#endif + +#if (NJT_HTTP_V2) + njt_http_v2_conf_t h2_conf; +#endif + +#if (NJT_HTTP_V3) + njt_quic_conf_t quic; +#endif + NJT_COMPAT_BEGIN(2) NJT_COMPAT_END } njt_http_upstream_conf_t; @@ -448,6 +467,15 @@ struct njt_http_upstream_s { unsigned request_body_sent:1; unsigned request_body_blocked:1; unsigned header_sent:1; +#if (NJT_HTTP_V2) + unsigned h2:1; + unsigned h2_init:1; +#endif +#if (NJT_HTTP_V3) + unsigned h3:1; + unsigned h3_started:1; + unsigned hq:1; +#endif }; diff --git a/src/http/v2/njt_http_v2.c b/src/http/v2/njt_http_v2.c index 7ce890eb..9a2f671c 100755 --- a/src/http/v2/njt_http_v2.c +++ b/src/http/v2/njt_http_v2.c @@ -3,6 +3,7 @@ * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. * Copyright (C) Valentin V. Bartenev + * Copyright (C) 2024 JD Technology Information Technology Co., Ltd. */ @@ -49,9 +50,6 @@ #define NJT_HTTP_V2_ROOT (void *) -1 - -static void njt_http_v2_read_handler(njt_event_t *rev); -static void njt_http_v2_write_handler(njt_event_t *wev); static void njt_http_v2_handle_connection(njt_http_v2_connection_t *h2c); static void njt_http_v2_lingering_close(njt_connection_t *c); static void njt_http_v2_lingering_close_handler(njt_event_t *rev); @@ -68,8 +66,6 @@ static u_char *njt_http_v2_state_read_data(njt_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *njt_http_v2_state_headers(njt_http_v2_connection_t *h2c, u_char *pos, u_char *end); -static u_char *njt_http_v2_state_header_block(njt_http_v2_connection_t *h2c, - u_char *pos, u_char *end); static u_char *njt_http_v2_state_field_len(njt_http_v2_connection_t *h2c, u_char *pos, u_char *end); static u_char *njt_http_v2_state_field_huff(njt_http_v2_connection_t *h2c, @@ -118,20 +114,10 @@ static u_char *njt_http_v2_connection_error(njt_http_v2_connection_t *h2c, static njt_int_t njt_http_v2_parse_int(njt_http_v2_connection_t *h2c, u_char **pos, u_char *end, njt_uint_t prefix); -static njt_http_v2_stream_t *njt_http_v2_create_stream( - njt_http_v2_connection_t *h2c); -static njt_http_v2_node_t *njt_http_v2_get_node_by_id( - njt_http_v2_connection_t *h2c, njt_uint_t sid, njt_uint_t alloc); static njt_http_v2_node_t *njt_http_v2_get_closed_node( njt_http_v2_connection_t *h2c); -#define njt_http_v2_index_size(h2scf) (h2scf->streams_index_mask + 1) -#define njt_http_v2_index(h2scf, sid) ((sid >> 1) & h2scf->streams_index_mask) - -static njt_int_t njt_http_v2_send_settings(njt_http_v2_connection_t *h2c); static njt_int_t njt_http_v2_settings_frame_handler( njt_http_v2_connection_t *h2c, njt_http_v2_out_frame_t *frame); -static njt_int_t njt_http_v2_send_window_update(njt_http_v2_connection_t *h2c, - njt_uint_t sid, size_t window); static njt_int_t njt_http_v2_send_rst_stream(njt_http_v2_connection_t *h2c, njt_uint_t sid, njt_uint_t status); static njt_int_t njt_http_v2_send_goaway(njt_http_v2_connection_t *h2c, @@ -171,17 +157,18 @@ static void njt_http_v2_close_stream_handler(njt_event_t *ev); static void njt_http_v2_retry_close_stream_handler(njt_event_t *ev); static void njt_http_v2_handle_connection_handler(njt_event_t *rev); static void njt_http_v2_idle_handler(njt_event_t *rev); -static void njt_http_v2_finalize_connection(njt_http_v2_connection_t *h2c, - njt_uint_t status); static njt_int_t njt_http_v2_adjust_windows(njt_http_v2_connection_t *h2c, ssize_t delta); -static void njt_http_v2_set_dependency(njt_http_v2_connection_t *h2c, - njt_http_v2_node_t *node, njt_uint_t depend, njt_uint_t exclusive); static void njt_http_v2_node_children_update(njt_http_v2_node_t *node); static void njt_http_v2_pool_cleanup(void *data); - +njt_chain_t *njt_http_v2_send_chain(njt_connection_t *fc, + njt_chain_t *in, off_t limit); +njt_int_t njt_http_v2_filter_send(njt_connection_t *fc, + njt_http_v2_stream_t *stream); +static njt_int_t njt_http_v2_upstream_process_header(njt_http_request_t *r, + njt_str_t *name, njt_str_t *value); static njt_http_v2_handler_pt njt_http_v2_frame_states[] = { njt_http_v2_state_data, /* NJT_HTTP_V2_DATA_FRAME */ @@ -326,7 +313,7 @@ njt_http_v2_init(njt_event_t *rev) } -static void +void njt_http_v2_read_handler(njt_event_t *rev) { u_char *p, *end; @@ -450,7 +437,7 @@ njt_http_v2_read_handler(njt_event_t *rev) } -static void +void njt_http_v2_write_handler(njt_event_t *wev) { njt_int_t rc; @@ -659,8 +646,8 @@ njt_http_v2_handle_connection(njt_http_v2_connection_t *h2c) clcf = njt_http_get_module_loc_conf(h2c->http_connection->conf_ctx, njt_http_core_module); - - if (!c->read->timer_set) { + //upstream connection disable keepalive + if (!c->read->timer_set && !h2c->client) { njt_add_timer(c->read, clcf->keepalive_timeout); } @@ -671,6 +658,9 @@ njt_http_v2_handle_connection(njt_http_v2_connection_t *h2c) } njt_destroy_pool(h2c->pool); + c->log = njt_cycle->log; + c->write->log = njt_cycle->log; + c->read->log = njt_cycle->log; h2c->pool = NULL; h2c->free_frames = NULL; @@ -691,6 +681,10 @@ njt_http_v2_handle_connection(njt_http_v2_connection_t *h2c) if (c->write->timer_set) { njt_del_timer(c->write); } + //upstream connection 用完即关 + if (h2c->client) { + njt_http_v2_finalize_connection(h2c, NJT_HTTP_V2_NO_ERROR); + } } @@ -1077,20 +1071,22 @@ njt_http_v2_state_read_data(njt_http_v2_connection_t *h2c, u_char *pos, } r = stream->request; - fc = r->connection; + fc = stream->fc; - if (r->reading_body && !r->request_body_no_buffering) { - njt_log_debug0(NJT_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "skipping http2 DATA frame"); + if (!h2c->client) { + if (r->reading_body && !r->request_body_no_buffering) { + njt_log_debug0(NJT_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "skipping http2 DATA frame"); - return njt_http_v2_state_skip_padded(h2c, pos, end); - } + return njt_http_v2_state_skip_padded(h2c, pos, end); + } - if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) { - njt_log_debug0(NJT_LOG_DEBUG_HTTP, h2c->connection->log, 0, - "skipping http2 DATA frame"); + if (r->headers_in.content_length_n < 0 && !r->headers_in.chunked) { + njt_log_debug0(NJT_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "skipping http2 DATA frame"); - return njt_http_v2_state_skip_padded(h2c, pos, end); + return njt_http_v2_state_skip_padded(h2c, pos, end); + } } size = end - pos; @@ -1102,42 +1098,54 @@ njt_http_v2_state_read_data(njt_http_v2_connection_t *h2c, u_char *pos, h2c->payload_bytes += size; - if (r->request_body) { - rc = njt_http_v2_process_request_body(r, pos, size, - stream->in_closed, 0); - - if (rc != NJT_OK && rc != NJT_AGAIN) { - stream->skip_data = 1; - njt_http_finalize_request(r, rc); + if (h2c->client) { + njt_log_debug2(NJT_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 write buffer sid:%ui size:%ui",h2c->state.sid, size); + if (size > 0) { + njt_http_v2_write_buffer(fc, pos, size); } + /* end stream*/ + fc->read->ready = 1; + njt_post_event(fc->read,&njt_posted_events); - njt_http_run_posted_requests(fc); + } else { + if (r->request_body) { + rc = njt_http_v2_process_request_body(r, pos, size, + stream->in_closed, 0); - } else if (size) { - buf = stream->preread; + if (rc != NJT_OK && rc != NJT_AGAIN) { + stream->skip_data = 1; + njt_http_finalize_request(r, rc); + } - if (buf == NULL) { - h2scf = njt_http_get_module_srv_conf(r, njt_http_v2_module); + njt_http_run_posted_requests(fc); + + } else if (size) { + buf = stream->preread; - buf = njt_create_temp_buf(r->pool, h2scf->preread_size); if (buf == NULL) { + h2scf = njt_http_get_module_srv_conf(r, njt_http_v2_module); + + buf = njt_create_temp_buf(r->pool, h2scf->preread_size); + if (buf == NULL) { + return njt_http_v2_connection_error(h2c, + NJT_HTTP_V2_INTERNAL_ERROR); + } + + stream->preread = buf; + } + + if (size > (size_t) (buf->end - buf->last)) { + njt_log_error(NJT_LOG_ALERT, h2c->connection->log, 0, + "http2 preread buffer overflow"); return njt_http_v2_connection_error(h2c, NJT_HTTP_V2_INTERNAL_ERROR); } - stream->preread = buf; + buf->last = njt_cpymem(buf->last, pos, size); } - - if (size > (size_t) (buf->end - buf->last)) { - njt_log_error(NJT_LOG_ALERT, h2c->connection->log, 0, - "http2 preread buffer overflow"); - return njt_http_v2_connection_error(h2c, - NJT_HTTP_V2_INTERNAL_ERROR); - } - - buf->last = njt_cpymem(buf->last, pos, size); } - + pos += size; h2c->state.length -= size; @@ -1259,23 +1267,37 @@ njt_http_v2_state_headers(njt_http_v2_connection_t *h2c, u_char *pos, return njt_http_v2_connection_error(h2c, NJT_HTTP_V2_PROTOCOL_ERROR); } - - h2c->last_sid = h2c->state.sid; - + + if (!h2c->client) { h2c->state.pool = njt_create_pool(1024, h2c->connection->log); if (h2c->state.pool == NULL) { return njt_http_v2_connection_error(h2c, NJT_HTTP_V2_INTERNAL_ERROR); } + } + + h2scf = njt_http_get_module_srv_conf(h2c->http_connection->conf_ctx, + njt_http_v2_module); + + if (!h2c->settings_ack + && !(h2c->state.flags & NJT_HTTP_V2_END_STREAM_FLAG) + && h2scf->preread_size < NJT_HTTP_V2_DEFAULT_WINDOW) + { + njt_log_error(NJT_LOG_INFO, h2c->connection->log, 0, + "client sent stream with data " + "before settings were acknowledged"); + + status = NJT_HTTP_V2_REFUSED_STREAM; + goto rst_stream; + } cscf = njt_http_get_module_srv_conf(h2c->http_connection->conf_ctx, njt_http_core_module); h2c->state.header_limit = cscf->large_client_header_buffers.size * cscf->large_client_header_buffers.num; - - h2scf = njt_http_get_module_srv_conf(h2c->http_connection->conf_ctx, - njt_http_v2_module); - + if (!h2c->client) { + + h2c->last_sid = h2c->state.sid; if (h2c->processing >= h2scf->concurrent_streams) { njt_log_error(NJT_LOG_INFO, h2c->connection->log, 0, "concurrent streams exceeded %ui", h2c->processing); @@ -1292,18 +1314,6 @@ njt_http_v2_state_headers(njt_http_v2_connection_t *h2c, u_char *pos, goto rst_stream; } - if (!h2c->settings_ack - && !(h2c->state.flags & NJT_HTTP_V2_END_STREAM_FLAG) - && h2scf->preread_size < NJT_HTTP_V2_DEFAULT_WINDOW) - { - njt_log_error(NJT_LOG_INFO, h2c->connection->log, 0, - "client sent stream with data " - "before settings were acknowledged"); - - status = NJT_HTTP_V2_REFUSED_STREAM; - goto rst_stream; - } - node = njt_http_v2_get_node_by_id(h2c, h2c->state.sid, 1); if (node == NULL) { @@ -1326,6 +1336,9 @@ njt_http_v2_state_headers(njt_http_v2_connection_t *h2c, u_char *pos, h2c->state.keep_pool = 1; stream->request->request_length = h2c->state.length; + stream->send_window = h2c->init_window; + stream->recv_window = h2scf->preread_size; + h2c->priority_limit += h2scf->concurrent_streams; stream->in_closed = h2c->state.flags & NJT_HTTP_V2_END_STREAM_FLAG; stream->node = node; @@ -1336,6 +1349,25 @@ njt_http_v2_state_headers(njt_http_v2_connection_t *h2c, u_char *pos, node->weight = weight; njt_http_v2_set_dependency(h2c, node, depend, excl); } + } else { + node = njt_http_v2_get_node_by_id(h2c, h2c->state.sid, 0); + + if (node == NULL) { + return njt_http_v2_connection_error(h2c, NJT_HTTP_V2_INTERNAL_ERROR); + } + stream = node->stream; + h2c->state.stream = stream; + h2c->state.keep_pool = 1; + + stream->state->flags = h2c->state.flags & NJT_HTTP_V2_END_HEADERS_FLAG; + stream->state->length = h2c->state.length; + stream->state->handler = njt_http_v2_state_header_block; + + stream->state->stream = stream; + stream->state->pool = stream->pool; + stream->state->keep_pool = 1; + stream->state->header_limit = h2c->state.header_limit; + } clcf = njt_http_get_module_loc_conf(h2c->http_connection->conf_ctx, njt_http_core_module); @@ -1371,13 +1403,14 @@ rst_stream: } -static u_char * +u_char * njt_http_v2_state_header_block(njt_http_v2_connection_t *h2c, u_char *pos, u_char *end) { u_char ch; njt_int_t value; njt_uint_t indexed, size_update, prefix; + njt_connection_t *fc; if (end - pos < 1) { return njt_http_v2_state_headers_save(h2c, pos, end, @@ -1391,6 +1424,19 @@ njt_http_v2_state_header_block(njt_http_v2_connection_t *h2c, u_char *pos, njt_http_v2_state_header_block); } + /*capture http2 header*/ + if (h2c->client && !h2c->state.parse) { + fc = h2c->state.stream->fc; + + size_t len = njt_min(h2c->state.length, (size_t) (end - pos)); + fc->read->ready = 1; + njt_http_v2_write_buffer(fc, pos, len); + njt_post_event(fc->read,&njt_posted_events); + h2c->state.length -= len; + pos += len; + + return njt_http_v2_state_header_complete(h2c, pos, end); + } size_update = 0; indexed = 0; @@ -1707,7 +1753,7 @@ njt_http_v2_state_process_header(njt_http_v2_connection_t *h2c, u_char *pos, size_t len; njt_int_t rc; njt_table_elt_t *h; - njt_connection_t *fc; + njt_connection_t *c; njt_http_header_t *hh; njt_http_request_t *r; njt_http_v2_header_t *header; @@ -1766,9 +1812,21 @@ njt_http_v2_state_process_header(njt_http_v2_connection_t *h2c, u_char *pos, return njt_http_v2_state_header_complete(h2c, pos, end); } - r = h2c->state.stream->request; - fc = r->connection; + r = h2c->state.stream->request; + + if (h2c->client) { + if (njt_http_v2_upstream_process_header(r, + &header->name, &header->value) != NJT_OK) { + + njt_http_v2_connection_error(h2c, NJT_HTTP_V2_INTERNAL_ERROR); + njt_http_finalize_request(r, NJT_HTTP_BAD_REQUEST); + h2c->state.stream = NULL; + return njt_http_v2_state_header_complete(h2c, pos, end); + } + return njt_http_v2_state_header_complete(h2c, pos, end); + } + c = r->connection; /* TODO Optimization: validate headers while parsing. */ if (njt_http_v2_validate_header(r, header) != NJT_OK) { njt_http_finalize_request(r, NJT_HTTP_BAD_REQUEST); @@ -1858,8 +1916,7 @@ error: h2c->state.stream = NULL; - njt_http_run_posted_requests(fc); - + njt_http_run_posted_requests(c); return njt_http_v2_state_header_complete(h2c, pos, end); } @@ -1887,7 +1944,7 @@ njt_http_v2_state_header_complete(njt_http_v2_connection_t *h2c, u_char *pos, stream = h2c->state.stream; - if (stream) { + if (stream && !h2c->client) { njt_http_v2_run_request(stream->request); } @@ -1913,6 +1970,7 @@ njt_http_v2_handle_continuation(njt_http_v2_connection_t *h2c, u_char *pos, u_char *p; size_t len, skip; uint32_t head; + njt_http_v2_stream_t *stream; len = h2c->state.length; @@ -1959,11 +2017,18 @@ njt_http_v2_handle_continuation(njt_http_v2_connection_t *h2c, u_char *pos, h2c->state.length += len; - if (h2c->state.stream) { + if (h2c->state.stream && !h2c->client && !h2c->fake ) { h2c->state.stream->request->request_length += len; } h2c->state.handler = handler; + + /*capture http2 continuation header*/ + if (h2c->client && !h2c->state.parse) { + stream = h2c->state.stream; + stream->state->flags = h2c->state.flags & NJT_HTTP_V2_END_HEADERS_FLAG; + stream->state->length =+ len; + } return pos; } @@ -2100,7 +2165,7 @@ njt_http_v2_state_rst_stream(njt_http_v2_connection_t *h2c, u_char *pos, stream->in_closed = 1; stream->out_closed = 1; - fc = stream->request->connection; + fc = stream->fc; fc->error = 1; switch (status) { @@ -2124,7 +2189,8 @@ njt_http_v2_state_rst_stream(njt_http_v2_connection_t *h2c, u_char *pos, } ev = fc->read; - ev->handler(ev); + //ev->handler(ev); + njt_post_event(ev, &njt_posted_next_events); return njt_http_v2_state_complete(h2c, pos, end); } @@ -2454,7 +2520,7 @@ njt_http_v2_state_window_update(njt_http_v2_connection_t *h2c, u_char *pos, if (stream->exhausted) { stream->exhausted = 0; - wev = stream->request->connection->write; + wev = stream->fc->write; wev->active = 0; wev->ready = 1; @@ -2489,7 +2555,7 @@ njt_http_v2_state_window_update(njt_http_v2_connection_t *h2c, u_char *pos, stream->waiting = 0; - wev = stream->request->connection->write; + wev = stream->fc->write; wev->active = 0; wev->ready = 1; @@ -2609,9 +2675,9 @@ njt_http_v2_state_headers_save(njt_http_v2_connection_t *h2c, u_char *pos, njt_http_request_t *r; njt_http_core_srv_conf_t *cscf; - if (h2c->state.stream) { + if (h2c->state.stream && !h2c->fake) { r = h2c->state.stream->request; - rev = r->connection->read; + rev = h2c->state.stream->fc->read; if (!rev->timer_set) { cscf = njt_http_get_module_srv_conf(r, njt_http_core_module); @@ -2630,6 +2696,9 @@ njt_http_v2_connection_error(njt_http_v2_connection_t *h2c, njt_log_debug0(NJT_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 state connection error"); + if (h2c->fake) { + return NULL; + } njt_http_v2_finalize_connection(h2c, err); return NULL; @@ -2692,7 +2761,7 @@ njt_http_v2_parse_int(njt_http_v2_connection_t *h2c, u_char **pos, u_char *end, } -static njt_int_t +njt_int_t njt_http_v2_send_settings(njt_http_v2_connection_t *h2c) { size_t len; @@ -2783,7 +2852,7 @@ njt_http_v2_settings_frame_handler(njt_http_v2_connection_t *h2c, } -static njt_int_t +njt_int_t njt_http_v2_send_window_update(njt_http_v2_connection_t *h2c, njt_uint_t sid, size_t window) { @@ -2962,7 +3031,7 @@ njt_http_v2_frame_handler(njt_http_v2_connection_t *h2c, } -static njt_http_v2_stream_t * +njt_http_v2_stream_t * njt_http_v2_create_stream(njt_http_v2_connection_t *h2c) { njt_log_t *log; @@ -2971,10 +3040,9 @@ njt_http_v2_create_stream(njt_http_v2_connection_t *h2c) njt_http_log_ctx_t *ctx; njt_http_request_t *r; njt_http_v2_stream_t *stream; - njt_http_v2_srv_conf_t *h2scf; - njt_http_core_srv_conf_t *cscf; + njt_http_core_srv_conf_t *cscf; - fc = h2c->free_fake_connections; + fc = h2c->free_fake_connections; if (fc) { h2c->free_fake_connections = fc->data; @@ -3018,7 +3086,10 @@ njt_http_v2_create_stream(njt_http_v2_connection_t *h2c) njt_memcpy(log, h2c->connection->log, sizeof(njt_log_t)); log->data = ctx; - log->action = "reading client request headers"; + log->action = "http2 stream request headers"; + + /*distinguish physical connections in logs*/ + log->connection = log->connection + 1000; njt_memzero(rev, sizeof(njt_event_t)); @@ -3026,10 +3097,14 @@ njt_http_v2_create_stream(njt_http_v2_connection_t *h2c) rev->ready = 1; rev->handler = njt_http_v2_close_stream_handler; rev->log = log; + rev->active = 1; njt_memcpy(wev, rev, sizeof(njt_event_t)); + wev->data = fc; + wev->log = log; wev->write = 1; + wev->active = 1; njt_memcpy(fc, h2c->connection, sizeof(njt_connection_t)); @@ -3042,6 +3117,11 @@ njt_http_v2_create_stream(njt_http_v2_connection_t *h2c) fc->sndlowat = 1; fc->tcp_nodelay = NJT_TCP_NODELAY_DISABLED; + stream = njt_pcalloc(h2c->pool, sizeof(njt_http_v2_stream_t)); + if (stream == NULL) { + return NULL; + } + if (!h2c->client) { r = njt_http_create_request(fc); if (r == NULL) { return NULL; @@ -3073,26 +3153,32 @@ njt_http_v2_create_stream(njt_http_v2_connection_t *h2c) } r->headers_in.connection_type = NJT_HTTP_CONNECTION_CLOSE; - - stream = njt_pcalloc(r->pool, sizeof(njt_http_v2_stream_t)); - if (stream == NULL) { - njt_http_free_request(r, NJT_HTTP_INTERNAL_SERVER_ERROR); + r->stream = stream; + stream->request = r; + } else { + stream->state = njt_pcalloc(h2c->pool, sizeof(njt_http_v2_state_t)); + if (stream->state == NULL) { return NULL; } + fc->log = njt_cycle->log; + fc->write->log = njt_cycle->log; + fc->read->log = njt_cycle->log; - r->stream = stream; - - stream->request = r; - stream->connection = h2c; - - h2scf = njt_http_get_module_srv_conf(r, njt_http_v2_module); - - stream->send_window = h2c->init_window; - stream->recv_window = h2scf->preread_size; - - h2c->processing++; + rev->ready = 0; + rev->active = 1; + wev->active = 1; + + fc->send = njt_http_v2_stream_send; + fc->recv = njt_http_v2_stream_recv; + fc->send_chain = njt_http_v2_send_chain; + fc->recv_chain = njt_http_v2_recv_chain; + } + stream->recv.last_chain = &stream->recv.chain; + fc->stream = stream; + stream->fc = fc; + stream->connection = h2c; - h2c->priority_limit += h2scf->concurrent_streams; + h2c->processing++; if (h2c->connection->read->timer_set) { njt_del_timer(h2c->connection->read); @@ -3102,7 +3188,7 @@ njt_http_v2_create_stream(njt_http_v2_connection_t *h2c) } -static njt_http_v2_node_t * +njt_http_v2_node_t * njt_http_v2_get_node_by_id(njt_http_v2_connection_t *h2c, njt_uint_t sid, njt_uint_t alloc) { @@ -4379,7 +4465,7 @@ njt_http_v2_terminate_stream(njt_http_v2_connection_t *h2c, stream->rst_sent = 1; stream->skip_data = 1; - fc = stream->request->connection; + fc = stream->fc; fc->error = 1; rev = fc->read; @@ -4405,7 +4491,7 @@ njt_http_v2_close_stream(njt_http_v2_stream_t *stream, njt_int_t rc) "http2 close stream %ui, queued %ui, processing %ui", node->id, stream->queued, h2c->processing); - fc = stream->request->connection; + fc = stream->fc; if (stream->queued) { fc->error = 1; @@ -4454,14 +4540,20 @@ njt_http_v2_close_stream(njt_http_v2_stream_t *stream, njt_int_t rc) h2c->frames -= stream->frames; - njt_http_free_request(stream->request, rc); - - if (pool != h2c->state.pool) { - njt_destroy_pool(pool); + if (!h2c->client) { + njt_http_free_request(stream->request, rc); + if (pool != h2c->state.pool) { + njt_destroy_pool(pool); + } else { + /* pool will be destroyed when the complete header is parsed */ + h2c->state.keep_pool = 0; + } } else { - /* pool will be destroyed when the complete header is parsed */ - h2c->state.keep_pool = 0; + if (pool == h2c->state.pool) { + h2c->state.pool = NULL; + } + njt_destroy_pool(pool); } ev = fc->read; @@ -4633,7 +4725,7 @@ njt_http_v2_idle_handler(njt_event_t *rev) njt_http_v2_finalize_connection(h2c, NJT_HTTP_V2_INTERNAL_ERROR); return; } - + njt_log_debug1(NJT_LOG_DEBUG_HTTP, c->log, 0, "http2 flood fd:%d",c->fd); c->write->handler = njt_http_v2_write_handler; rev->handler = njt_http_v2_read_handler; @@ -4641,14 +4733,13 @@ njt_http_v2_idle_handler(njt_event_t *rev) } -static void +void njt_http_v2_finalize_connection(njt_http_v2_connection_t *h2c, njt_uint_t status) { njt_uint_t i, size; njt_event_t *ev; - njt_connection_t *c, *fc; - njt_http_request_t *r; + njt_connection_t *c, *fc; njt_http_v2_node_t *node; njt_http_v2_stream_t *stream; njt_http_v2_srv_conf_t *h2scf; @@ -4665,6 +4756,8 @@ njt_http_v2_finalize_connection(njt_http_v2_connection_t *h2c, } } + njt_log_debug2(NJT_LOG_DEBUG_HTTP, c->log, 0, "http2 finalize connection fd:%d, processing:%d",c->fd, h2c->processing); + if (!h2c->processing) { goto done; } @@ -4690,9 +4783,7 @@ njt_http_v2_finalize_connection(njt_http_v2_connection_t *h2c, stream->waiting = 0; - r = stream->request; - fc = r->connection; - + fc = stream->fc; fc->error = 1; if (stream->queued) { @@ -4724,8 +4815,12 @@ done: njt_http_close_connection(c); return; } - - njt_http_v2_lingering_close(c); + if (!h2c->client) { + njt_http_v2_lingering_close(c); + } else { + njt_http_close_connection(c); + } + } @@ -4775,7 +4870,7 @@ njt_http_v2_adjust_windows(njt_http_v2_connection_t *h2c, ssize_t delta) if (stream->send_window > 0 && stream->exhausted) { stream->exhausted = 0; - wev = stream->request->connection->write; + wev = stream->fc->write; wev->active = 0; wev->ready = 1; @@ -4791,7 +4886,7 @@ njt_http_v2_adjust_windows(njt_http_v2_connection_t *h2c, ssize_t delta) } -static void +void njt_http_v2_set_dependency(njt_http_v2_connection_t *h2c, njt_http_v2_node_t *node, njt_uint_t depend, njt_uint_t exclusive) { @@ -4915,3 +5010,339 @@ njt_http_v2_pool_cleanup(void *data) njt_destroy_pool(h2c->pool); } } + +njt_int_t +njt_http_v2_create_client(njt_http_v2_conf_t *conf, njt_connection_t *c) +{ + njt_http_v2_main_conf_t *h2mcf; + njt_http_v2_srv_conf_t *h2scf; + njt_http_connection_t *hc; + njt_http_v2_connection_t *h2c; + njt_pool_cleanup_t *cln; + njt_uint_t id; + njt_http_v2_node_t *node; + njt_log_t *log; + + hc = c->data; + + h2mcf = njt_http_get_module_main_conf(hc->conf_ctx, njt_http_v2_module); + + if (h2mcf->recv_buffer == NULL) { + h2mcf->recv_buffer = njt_palloc(njt_cycle->pool, + h2mcf->recv_buffer_size); + if (h2mcf->recv_buffer == NULL) { + return NJT_ERROR; + } + } + + h2c = njt_pcalloc(c->pool, sizeof(njt_http_v2_connection_t)); + if (h2c == NULL) { + return NJT_ERROR; + } + + h2c->connection = c; + h2c->http_connection = hc; + c->data = h2c; + + h2c->send_window = NJT_HTTP_V2_DEFAULT_WINDOW; + h2c->recv_window = NJT_HTTP_V2_MAX_WINDOW; + + h2c->init_window = NJT_HTTP_V2_DEFAULT_WINDOW; + + h2c->frame_size = NJT_HTTP_V2_DEFAULT_FRAME_SIZE; + + h2scf = njt_http_get_module_srv_conf(hc->conf_ctx, njt_http_v2_module); + + h2c->priority_limit = njt_max(conf->concurrent_streams, 100); + + //h2c->state.handler = njt_http_v2_upstream_state_handler; + h2c->state.handler = njt_http_v2_state_head; + njt_queue_init(&h2c->waiting); + njt_queue_init(&h2c->dependencies); + njt_queue_init(&h2c->closed); + + h2c->client = 1; + h2c->pool = njt_create_pool(h2scf->pool_size, h2c->connection->log); + if (h2c->pool == NULL) { + return NJT_ERROR; + } + + cln = njt_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + return NJT_ERROR; + } + + cln->handler = njt_http_v2_pool_cleanup; + cln->data = h2c; + + log = njt_palloc(h2c->pool, sizeof(njt_log_t)); + if (log == NULL) { + return NJT_ERROR; + } + + *log = *njt_cycle->log; + log->connection = c->number; + + c->log = log; + c->read->log = log; + c->write->log = log; + c->pool->log = log; + h2c->pool->log = log; + + h2c->streams_index = njt_pcalloc(c->pool, njt_http_v2_index_size(h2scf) + * sizeof(njt_http_v2_node_t *)); + if (h2c->streams_index == NULL) { + return NJT_ERROR; + } + --h2c->priority_limit; + + h2c->last_sid++; + id = (h2c->last_sid << 1) -1; + node = njt_http_v2_get_node_by_id(h2c,id,1); + if (node == NULL) { + return NJT_ERROR; + } + node->weight = 200; + h2c->closed_nodes++; + njt_queue_insert_tail(&h2c->closed,&node->reuse); + njt_http_v2_set_dependency(h2c, node, 0, 0); + + return NJT_OK; +} + +njt_int_t +njt_http_v2_send_preface(njt_http_v2_connection_t *h2c) { + size_t len; + njt_buf_t *buf; + njt_chain_t *cl; + njt_http_v2_out_frame_t *frame; + + njt_log_debug0(NJT_LOG_DEBUG_HTTP, h2c->connection->log, 0, + "http2 send PREFACE frame"); + + frame = njt_palloc(h2c->pool, sizeof(njt_http_v2_out_frame_t)); + if (frame == NULL) { + return NJT_ERROR; + } + + cl = njt_alloc_chain_link(h2c->pool); + if (cl == NULL) { + return NJT_ERROR; + } + + len = sizeof(NJT_HTTP_V2_PREFACE) - 1; + + buf = njt_create_temp_buf(h2c->pool, len); + if (buf == NULL) { + return NJT_ERROR; + } + + //buf->last_buf = 1; + + cl->buf = buf; + cl->next = NULL; + + frame->first = cl; + frame->last = cl; + frame->handler = njt_http_v2_settings_frame_handler; + frame->stream = NULL; +#if (NJT_DEBUG) + frame->length = len; +#endif + frame->blocked = 1; + + buf->last = njt_cpymem(buf->last, NJT_HTTP_V2_PREFACE, len); + + njt_http_v2_queue_blocked_frame(h2c, frame); + + return NJT_OK; +} + + +/*static u_char * +njt_http_v2_upstream_state_handler(njt_http_v2_connection_t *h2c, u_char *pos, + u_char *end) +{ + njt_log_debug1(NJT_LOG_DEBUG_CORE, h2c->connection->log, 0, + "njt_http_v2_upstream_state_handler size: %d", end-pos); + return end; +}*/ + +static njt_int_t +njt_http_v2_upstream_process_pseudo_header(njt_http_request_t *r, njt_str_t *name, + njt_str_t *value) +{ + njt_int_t status; + njt_str_t *status_line; + njt_http_upstream_t *u; + + /* based on njt_http_v2_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 && njt_strncmp(name->data, ":status", 7) == 0) { + + if (u->state && u->state->status +#if (NJT_HTTP_CACHE) + && !r->cached +#endif + ) { + njt_log_error(NJT_LOG_INFO, r->connection->log, 0, + "upstream sent duplicate \":status\" header"); + return NJT_ERROR; + } + + if (value->len == 0) { + njt_log_error(NJT_LOG_INFO, r->connection->log, 0, + "upstream sent empty \":status\" header"); + return NJT_ERROR; + } + + if (value->len < 3) { + njt_log_error(NJT_LOG_INFO, r->connection->log, 0, + "upstream sent too short \":status\" header"); + return NJT_ERROR; + } + + status = njt_atoi(value->data, 3); + + if (status == NJT_ERROR) { + njt_log_error(NJT_LOG_ERR, r->connection->log, 0, + "upstream sent invalid status \"%V\"", value); + return NJT_ERROR; + } + + if (u->state && u->state->status == 0) { + u->state->status = status; + } + + u->headers_in.status_n = status; + + status_line = njt_http_status_line(status); + if (status_line) { + u->headers_in.status_line = *status_line; + } + + njt_log_debug2(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "http v2 proxy status %ui \"%V\"", + u->headers_in.status_n, &u->headers_in.status_line); + + return NJT_OK; + } + + njt_log_error(NJT_LOG_INFO, r->connection->log, 0, + "upstream sent unexpected pseudo-header \"%V\"", name); + + return NJT_ERROR; +} + +static njt_int_t +njt_http_v2_upstream_process_header(njt_http_request_t *r,njt_str_t *name, + njt_str_t *value) +{ + njt_table_elt_t *h; + njt_http_upstream_t *u; + njt_http_upstream_header_t *hh; + njt_http_upstream_main_conf_t *umcf; + + /* based on njt_http_v2_process_header() */ + + umcf = njt_http_get_module_main_conf(r, njt_http_upstream_module); + u = r->upstream; + + if (name->len && name->data[0] == ':') { + return njt_http_v2_upstream_process_pseudo_header(r, name, value); + } + + h = njt_list_push(&u->headers_in.headers); + if (h == NULL) { + return NJT_ERROR; + } + + /* + * HTTP/2 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 = njt_pnalloc(r->pool, name->len + 1); + if (h->key.data == NULL) { + return NJT_ERROR; + } + njt_memcpy(h->key.data, name->data, name->len); + h->key.data[h->key.len] = 0; + + h->value.len = value->len; + h->value.data = njt_pnalloc(r->pool, value->len + 1); + if (h->value.data == NULL) { + return NJT_ERROR; + } + njt_memcpy(h->value.data, value->data, value->len); + h->value.data[h->value.len] = 0; + + h->lowcase_key = h->key.data; + h->hash = njt_hash_key(h->key.data, h->key.len); + + hh = njt_hash_find(&umcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NJT_OK) { + return NJT_ERROR; + } + + njt_log_debug2(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "http2 header: \"%V: %V\"", name, value); + return NJT_OK; +} + +njt_int_t +njt_http_v2_parse_headers(njt_http_v2_connection_t *ph2, njt_buf_t *b) +{ + u_char *p, *end; + + ph2->state.buffer_used = 0; + ph2->state.incomplete = 0; + + p = b->pos; + end = b->last; + + do { + p = ph2->state.handler(ph2, p, end); + + if (p == NULL) { + njt_log_debug3(NJT_LOG_INFO, ph2->connection->log, 0, + "njt_http_v2_parse_headers pos:%p, end:%p, current:%p", + b->pos, end, p); + return NJT_ERROR; + } + if (ph2->state.length == 0 && + ph2->state.flags & NJT_HTTP_V2_END_HEADERS_FLAG) { + ph2->state.handler = njt_http_v2_state_header_block; + //h2c->state.stream = stream; + break; + } + + } while (p != end); + + if (ph2->state.incomplete) { + b->pos = p - ph2->state.buffer_used; + ph2->state.incomplete = 0; + ph2->state.buffer_used = 0; + } else { + b->pos = p; + } + + if (ph2->state.length == 0 && + ph2->state.flags & NJT_HTTP_V2_END_HEADERS_FLAG) { + return NJT_OK; + } + + return NJT_AGAIN; +} \ No newline at end of file diff --git a/src/http/v2/njt_http_v2.h b/src/http/v2/njt_http_v2.h index afe2c3e1..8b671146 100755 --- a/src/http/v2/njt_http_v2.h +++ b/src/http/v2/njt_http_v2.h @@ -13,6 +13,7 @@ #include #include +#include #define NJT_HTTP_V2_ALPN_PROTO "\x02h2" @@ -89,6 +90,7 @@ typedef struct { unsigned parse_name:1; unsigned parse_value:1; unsigned index:1; + unsigned parse:1; njt_http_v2_header_t header; size_t header_limit; u_char field_state; @@ -124,6 +126,7 @@ typedef struct { struct njt_http_v2_connection_s { njt_connection_t *connection; njt_http_connection_t *http_connection; + void *init_ssl_data; off_t total_bytes; off_t payload_bytes; @@ -164,10 +167,20 @@ struct njt_http_v2_connection_s { time_t lingering_time; + njt_buf_t *free_bufs; + njt_buf_t *free_shadow_bufs; + +#ifdef NJT_HTTP_V2_DEBUG_ALLOC + njt_uint_t nbufs; + njt_uint_t nshadowbufs; +#endif + unsigned settings_ack:1; unsigned table_update:1; unsigned blocked:1; unsigned goaway:1; + unsigned client:1; + unsigned fake:1; }; @@ -189,6 +202,7 @@ struct njt_http_v2_stream_s { njt_http_request_t *request; njt_http_v2_connection_t *connection; njt_http_v2_node_t *node; + njt_connection_t *fc; njt_uint_t queued; @@ -212,6 +226,8 @@ struct njt_http_v2_stream_s { njt_array_t *cookies; njt_pool_t *pool; + njt_http_v2_stream_buffer_t recv; + njt_http_v2_state_t *state; unsigned waiting:1; unsigned blocked:1; @@ -311,7 +327,27 @@ njt_int_t njt_http_v2_add_header(njt_http_v2_connection_t *h2c, njt_http_v2_header_t *header); njt_int_t njt_http_v2_table_size(njt_http_v2_connection_t *h2c, size_t size); +njt_int_t njt_http_v2_send_preface(njt_http_v2_connection_t *h2c); +njt_int_t njt_http_v2_send_settings(njt_http_v2_connection_t *h2c); +void njt_http_v2_read_handler(njt_event_t *rev); +void njt_http_v2_write_handler(njt_event_t *wev); +njt_int_t njt_http_v2_create_client(njt_http_v2_conf_t *conf, njt_connection_t *c); +njt_http_v2_stream_t *njt_http_v2_create_stream( + njt_http_v2_connection_t *h2c); +njt_http_v2_node_t *njt_http_v2_get_node_by_id( + njt_http_v2_connection_t *h2c, njt_uint_t sid, njt_uint_t alloc); +void njt_http_v2_set_dependency(njt_http_v2_connection_t *h2c, + njt_http_v2_node_t *node, njt_uint_t depend, njt_uint_t exclusive); +void njt_http_v2_finalize_connection(njt_http_v2_connection_t *h2c, + njt_uint_t status); +njt_int_t njt_http_v2_send_window_update(njt_http_v2_connection_t *h2c, + njt_uint_t sid, size_t window); +njt_int_t njt_http_v2_parse_headers(njt_http_v2_connection_t *ph2, njt_buf_t *b); +u_char *njt_http_v2_state_header_block(njt_http_v2_connection_t *h2c, + u_char *pos, u_char *end); +#define njt_http_v2_index_size(h2scf) (h2scf->streams_index_mask + 1) +#define njt_http_v2_index(h2scf, sid) ((sid >> 1) & h2scf->streams_index_mask) #define njt_http_v2_prefix(bits) ((1 << (bits)) - 1) diff --git a/src/http/v2/njt_http_v2_filter_module.c b/src/http/v2/njt_http_v2_filter_module.c index 91fc8c95..31b8c58a 100755 --- a/src/http/v2/njt_http_v2_filter_module.c +++ b/src/http/v2/njt_http_v2_filter_module.c @@ -4,6 +4,7 @@ * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. * Copyright (C) Valentin V. Bartenev * Copyright (C) Ruslan Ermilov + * Copyright (C) 2024 JD Technology Information Technology Co., Ltd. */ @@ -28,12 +29,14 @@ #define NJT_HTTP_V2_NO_TRAILERS (njt_http_v2_out_frame_t *) -1 -static njt_http_v2_out_frame_t *njt_http_v2_create_headers_frame( - njt_http_request_t *r, u_char *pos, u_char *end, njt_uint_t fin); +njt_http_v2_out_frame_t *njt_http_v2_create_headers_frame( + njt_http_v2_stream_t *stream, u_char *pos, u_char *end, njt_uint_t fin); static njt_http_v2_out_frame_t *njt_http_v2_create_trailers_frame( njt_http_request_t *r); -static njt_chain_t *njt_http_v2_send_chain(njt_connection_t *fc, +njt_chain_t *njt_http_v2_send_chain(njt_connection_t *fc, + njt_chain_t *in, off_t limit); +ssize_t njt_http_v2_recv_chain(njt_connection_t *fc, njt_chain_t *in, off_t limit); static njt_chain_t *njt_http_v2_filter_get_shadow( @@ -47,7 +50,7 @@ static njt_inline njt_int_t njt_http_v2_flow_control( static void njt_http_v2_waiting_queue(njt_http_v2_connection_t *h2c, njt_http_v2_stream_t *stream); -static njt_inline njt_int_t njt_http_v2_filter_send( +njt_int_t njt_http_v2_filter_send( njt_connection_t *fc, njt_http_v2_stream_t *stream); static njt_int_t njt_http_v2_headers_frame_handler( @@ -59,7 +62,7 @@ static njt_inline void njt_http_v2_handle_frame( static njt_inline void njt_http_v2_handle_stream( njt_http_v2_connection_t *h2c, njt_http_v2_stream_t *stream); -static void njt_http_v2_filter_cleanup(void *data); +void njt_http_v2_filter_cleanup(void *data); static njt_int_t njt_http_v2_filter_init(njt_conf_t *cf); @@ -605,7 +608,7 @@ njt_http_v2_header_filter(njt_http_request_t *r) fin = r->header_only || (r->headers_out.content_length_n == 0 && !r->expect_trailers); - frame = njt_http_v2_create_headers_frame(r, start, pos, fin); + frame = njt_http_v2_create_headers_frame(r->stream, start, pos, fin); if (frame == NULL) { return NJT_ERROR; } @@ -630,21 +633,21 @@ njt_http_v2_header_filter(njt_http_request_t *r) } -static njt_http_v2_out_frame_t * -njt_http_v2_create_headers_frame(njt_http_request_t *r, u_char *pos, +njt_http_v2_out_frame_t * +njt_http_v2_create_headers_frame(njt_http_v2_stream_t *stream, u_char *pos, u_char *end, njt_uint_t fin) { u_char type, flags; size_t rest, frame_size; njt_buf_t *b; njt_chain_t *cl, **ll; - njt_http_v2_stream_t *stream; + //njt_http_v2_connection_t *h2c; njt_http_v2_out_frame_t *frame; - stream = r->stream; + //h2c = stream->connection; rest = end - pos; - frame = njt_palloc(r->pool, sizeof(njt_http_v2_out_frame_t)); + frame = njt_palloc(stream->request->pool, sizeof(njt_http_v2_out_frame_t)); if (frame == NULL) { return NULL; } @@ -667,7 +670,7 @@ njt_http_v2_create_headers_frame(njt_http_request_t *r, u_char *pos, flags |= NJT_HTTP_V2_END_HEADERS_FLAG; } - b = njt_create_temp_buf(r->pool, NJT_HTTP_V2_FRAME_HEADER_SIZE); + b = njt_create_temp_buf(stream->request->pool, NJT_HTTP_V2_FRAME_HEADER_SIZE); if (b == NULL) { return NULL; } @@ -678,7 +681,7 @@ njt_http_v2_create_headers_frame(njt_http_request_t *r, u_char *pos, b->tag = (njt_buf_tag_t) &njt_http_v2_module; - cl = njt_alloc_chain_link(r->pool); + cl = njt_alloc_chain_link(stream->request->pool); if (cl == NULL) { return NULL; } @@ -688,7 +691,7 @@ njt_http_v2_create_headers_frame(njt_http_request_t *r, u_char *pos, *ll = cl; ll = &cl->next; - b = njt_calloc_buf(r->pool); + b = njt_calloc_buf(stream->request->pool); if (b == NULL) { return NULL; } @@ -702,7 +705,7 @@ njt_http_v2_create_headers_frame(njt_http_request_t *r, u_char *pos, b->end = b->last; b->temporary = 1; - cl = njt_alloc_chain_link(r->pool); + cl = njt_alloc_chain_link(stream->request->pool); if (cl == NULL) { return NULL; } @@ -726,7 +729,7 @@ njt_http_v2_create_headers_frame(njt_http_request_t *r, u_char *pos, cl->next = NULL; frame->last = cl; - njt_log_debug4(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + njt_log_debug4(NJT_LOG_DEBUG_HTTP, stream->fc->log, 0, "http2:%ui create HEADERS frame %p: len:%uz fin:%ui", stream->node->id, frame, frame->length, fin); @@ -845,11 +848,11 @@ njt_http_v2_create_trailers_frame(njt_http_request_t *r) header[i].value.len, tmp); } - return njt_http_v2_create_headers_frame(r, start, pos, 1); + return njt_http_v2_create_headers_frame(r->stream, start, pos, 1); } -static njt_chain_t * +njt_chain_t * njt_http_v2_send_chain(njt_connection_t *fc, njt_chain_t *in, off_t limit) { off_t size, offset; @@ -862,7 +865,7 @@ njt_http_v2_send_chain(njt_connection_t *fc, njt_chain_t *in, off_t limit) njt_http_v2_connection_t *h2c; r = fc->data; - stream = r->stream; + stream = fc->stream; #if (NJT_SUPPRESS_WARN) size = 0; @@ -1248,7 +1251,7 @@ njt_http_v2_waiting_queue(njt_http_v2_connection_t *h2c, } -static njt_inline njt_int_t +njt_int_t njt_http_v2_filter_send(njt_connection_t *fc, njt_http_v2_stream_t *stream) { njt_connection_t *c; @@ -1324,9 +1327,10 @@ njt_http_v2_headers_frame_handler(njt_http_v2_connection_t *h2c, njt_log_debug2(NJT_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2:%ui HEADERS frame %p was sent", stream->node->id, frame); - - stream->request->header_size += NJT_HTTP_V2_FRAME_HEADER_SIZE + if (!h2c->client) { + stream->request->header_size += NJT_HTTP_V2_FRAME_HEADER_SIZE + frame->length; + } h2c->payload_bytes += frame->length; @@ -1420,8 +1424,9 @@ done: njt_log_debug2(NJT_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2:%ui DATA frame %p was sent", stream->node->id, frame); - - stream->request->header_size += NJT_HTTP_V2_FRAME_HEADER_SIZE; + if (!h2c->client) { + stream->request->header_size += NJT_HTTP_V2_FRAME_HEADER_SIZE; + } h2c->payload_bytes += frame->length; @@ -1437,12 +1442,12 @@ static njt_inline void njt_http_v2_handle_frame(njt_http_v2_stream_t *stream, njt_http_v2_out_frame_t *frame) { - njt_http_request_t *r; njt_http_v2_connection_t *h2c; + njt_connection_t *fc; - r = stream->request; + fc = stream->fc; - r->connection->sent += NJT_HTTP_V2_FRAME_HEADER_SIZE + frame->length; + fc->sent += NJT_HTTP_V2_FRAME_HEADER_SIZE + frame->length; h2c = stream->connection; @@ -1470,7 +1475,7 @@ njt_http_v2_handle_stream(njt_http_v2_connection_t *h2c, return; } - fc = stream->request->connection; + fc = stream->fc; if (!fc->error && stream->exhausted) { return; @@ -1489,7 +1494,7 @@ njt_http_v2_handle_stream(njt_http_v2_connection_t *h2c, } -static void +void njt_http_v2_filter_cleanup(void *data) { njt_http_v2_stream_t *stream = data; @@ -1546,7 +1551,7 @@ njt_http_v2_filter_cleanup(void *data) stream->waiting = 0; - wev = stream->request->connection->write; + wev = stream->fc->write; wev->active = 0; wev->ready = 1; diff --git a/src/http/v2/njt_http_v2_stream.c b/src/http/v2/njt_http_v2_stream.c new file mode 100644 index 00000000..d78bf0b7 --- /dev/null +++ b/src/http/v2/njt_http_v2_stream.c @@ -0,0 +1,885 @@ +/* + * Copyright (C) 2024 Kern + */ + +#include +#include +#include + + +#define NJT_HTTP_V2_BUFFER_SIZE 4096 + +#define njt_http_v2_buf_refs(b) (b)->shadow->num +#define njt_http_v2_buf_inc_refs(b) njt_http_v2_buf_refs(b)++ +#define njt_http_v2_buf_dec_refs(b) njt_http_v2_buf_refs(b)-- +#define njt_http_v2_buf_set_refs(b, v) njt_http_v2_buf_refs(b) = v + + +static njt_buf_t *njt_http_v2_alloc_buf(njt_connection_t *c); +static void njt_http_v2_free_buf(njt_connection_t *c, njt_buf_t *b); +static njt_buf_t *njt_http_v2_clone_buf(njt_connection_t *c, njt_buf_t *b); +static njt_int_t njt_http_v2_split_chain(njt_connection_t *c, njt_chain_t *cl, + off_t offset); +njt_chain_t *njt_http_v2_write_buffer_chain(njt_connection_t *c, njt_http_v2_stream_buffer_t *qb, + njt_chain_t *in, uint64_t limit, uint64_t offset); + +static njt_buf_t * +njt_http_v2_alloc_buf(njt_connection_t *c) +{ + u_char *p; + njt_buf_t *b; + njt_http_v2_connection_t *h2c; + + h2c = njt_http_v2_get_connection(c); + + b = h2c->free_bufs; + + if (b) { + h2c->free_bufs = b->shadow; + p = b->start; + + } else { + b = h2c->free_shadow_bufs; + + if (b) { + h2c->free_shadow_bufs = b->shadow; + +#ifdef NJT_HTTP_V2_DEBUG_ALLOC + njt_log_debug2(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic use shadow buffer n:%ui %ui", + ++h2c->nbufs, --h2c->nshadowbufs); +#endif + + } else { + b = njt_palloc(h2c->pool, sizeof(njt_buf_t)); + if (b == NULL) { + return NULL; + } + +#ifdef NJT_HTTP_V2_DEBUG_ALLOC + njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic new buffer n:%ui", ++h2c->nbufs); +#endif + } + + p = njt_pnalloc(h2c->pool, NJT_HTTP_V2_BUFFER_SIZE); + if (p == NULL) { + return NULL; + } + } + +#ifdef NJT_HTTP_V2_DEBUG_ALLOC + njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, "quic alloc buffer %p", b); +#endif + + njt_memzero(b, sizeof(njt_buf_t)); + + b->tag = (njt_buf_tag_t) &njt_http_v2_alloc_buf; + b->temporary = 1; + b->shadow = b; + + b->start = p; + b->pos = p; + b->last = p; + b->end = p + NJT_HTTP_V2_BUFFER_SIZE; + + njt_http_v2_buf_set_refs(b, 1); + + return b; +} + + +static void +njt_http_v2_free_buf(njt_connection_t *c, njt_buf_t *b) +{ + njt_buf_t *shadow; + njt_http_v2_connection_t *h2c; + + h2c = njt_http_v2_get_connection(c); + + njt_http_v2_buf_dec_refs(b); + +#ifdef NJT_HTTP_V2_DEBUG_ALLOC + njt_log_debug2(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic free buffer %p r:%ui", + b, (njt_uint_t) njt_http_v2_buf_refs(b)); +#endif + + shadow = b->shadow; + + if (njt_http_v2_buf_refs(b) == 0) { + shadow->shadow = h2c->free_bufs; + h2c->free_bufs = shadow; + } + + if (b != shadow) { + b->shadow = h2c->free_shadow_bufs; + h2c->free_shadow_bufs = b; + } + +} + + +static njt_buf_t * +njt_http_v2_clone_buf(njt_connection_t *c, njt_buf_t *b) +{ + njt_buf_t *nb; + njt_http_v2_connection_t *h2c; + + h2c = njt_http_v2_get_connection(c); + + nb = h2c->free_shadow_bufs; + + if (nb) { + h2c->free_shadow_bufs = nb->shadow; + + } else { + nb = njt_palloc(h2c->pool, sizeof(njt_buf_t)); + if (nb == NULL) { + return NULL; + } + +#ifdef NJT_HTTP_V2_DEBUG_ALLOC + njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic new shadow buffer n:%ui", ++h2c->nshadowbufs); +#endif + } + + *nb = *b; + + njt_http_v2_buf_inc_refs(b); + +#ifdef NJT_HTTP_V2_DEBUG_ALLOC + njt_log_debug3(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic clone buffer %p %p r:%ui", + b, nb, (njt_uint_t) njt_http_v2_buf_refs(b)); +#endif + + return nb; +} + +static njt_int_t +njt_http_v2_split_chain(njt_connection_t *c, njt_chain_t *cl, off_t offset) +{ + njt_buf_t *b, *tb; + njt_chain_t *tail; + + b = cl->buf; + + tail = njt_alloc_chain_link(c->pool); + if (tail == NULL) { + return NJT_ERROR; + } + + tb = njt_http_v2_clone_buf(c, b); + if (tb == NULL) { + return NJT_ERROR; + } + + tail->buf = tb; + + tb->pos += offset; + + b->last = tb->pos; + b->last_buf = 0; + + tail->next = cl->next; + cl->next = tail; + + return NJT_OK; +} + +/*void +njt_http_v2_out_chain( njt_chain_t *out, int w) +{ + njt_chain_t *cl; + int i = 0; + while (out) { + cl = out; + out = out->next; + printf("%s:%d -- cl:%p, next:%p , pnext:%p, buf:%p, sync:%d, len:%ld\n", + w ==1 ? "write":"read",i++, cl,cl->next,&cl->next,cl->buf,cl->buf->sync, + cl->buf->last - cl->buf->pos); + } +}*/ + +njt_chain_t * +njt_http_v2_read_buffer(njt_connection_t *c, njt_http_v2_stream_buffer_t *qb, uint64_t limit) +{ + uint64_t n; + njt_buf_t *b; + njt_chain_t *out, **ll; + + out = qb->chain; + for (ll = &out; *ll; ll = &(*ll)->next) { + b = (*ll)->buf; + + if (b->sync) { + /*not used*/ + break;; + } + if (limit == 0) { + break; + } + + n = b->last - b->pos; + + if (n > limit) { + if (njt_http_v2_split_chain(c, *ll, limit) != NJT_OK) { + return NJT_CHAIN_ERROR; + } + if (qb->last_chain == &(*ll)->next) { + qb->last_chain = &(*ll)->next->next; + } + n = limit; + } + + qb->size -= n; + limit -= n; + } + + if (*ll == NULL || qb->last_chain == ll) { + qb->last_chain = &qb->chain; + } + + qb->chain = *ll; + *ll = NULL; + + if (out && out->buf->sync) { + return NULL; + } + + return out; +} + +njt_chain_t * +njt_http_v2_alloc_chain(njt_connection_t *c) +{ + njt_chain_t *cl; + + cl = njt_alloc_chain_link(c->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = njt_http_v2_alloc_buf(c); + if (cl->buf == NULL) { + return NULL; + } + + return cl; +} + +njt_int_t +njt_http_v2_write_buffer(njt_connection_t *c, u_char *data,size_t len) { + njt_buf_t buf; + njt_chain_t cl; + njt_http_v2_stream_t *stream; + + stream = c->stream; + + njt_memzero(&buf, sizeof(njt_buf_t)); + + buf.pos = data; + buf.last = buf.pos + len; + buf.temporary = 1; + + cl.buf = &buf; + cl.next = NULL; + + if (njt_http_v2_write_chain(c, &stream->recv, &cl, len) == NJT_CHAIN_ERROR) { + return NJT_ERROR; + } + return NJT_OK; +} + + +njt_chain_t * +njt_http_v2_write_chain(njt_connection_t *c, njt_http_v2_stream_buffer_t *qb, + njt_chain_t *in, uint64_t limit) +{ + u_char *p; + uint64_t n; + njt_buf_t *b; + njt_chain_t *cl, **chain; + + chain = qb->last_chain; + while (in && limit) { + + cl = *chain; + if (cl == NULL) { + cl = njt_http_v2_alloc_chain(c); + if (cl == NULL) { + return NJT_CHAIN_ERROR; + } + + cl->buf->last = cl->buf->end; + cl->buf->sync = 1; /* not used */ + cl->next = NULL; + *chain = cl; + } + + b = cl->buf; + p = b->pos; + while (in) { + + if (!njt_buf_in_memory(in->buf) || in->buf->pos == in->buf->last) { + in = in->next; + continue; + } + + if (p == b->last || limit == 0) { + break; + } + + n = njt_min(b->last - p, in->buf->last - in->buf->pos); + n = njt_min(n, limit); + + + njt_memcpy(p, in->buf->pos, n); + qb->size += n; + + p += n; + in->buf->pos += n; + limit -= n; + } + + if (b->sync && p == b->last) { + b->sync = 0; + chain = &cl->next; + continue; + } + + if (b->sync && p != b->pos) { + if (njt_http_v2_split_chain(c, cl, p - b->pos) != NJT_OK) { + return NJT_CHAIN_ERROR; + } + b->sync = 0; + chain = &cl->next; + } + } + qb->last_chain = chain; + return in; +} + +void +njt_http_v2_free_chain(njt_connection_t *c, njt_chain_t *in) +{ + njt_chain_t *cl; + njt_http_v2_connection_t *h2c; + + h2c = njt_http_v2_get_connection(c); + + while (in) { + cl = in; + in = in->next; + + njt_http_v2_free_buf(c, cl->buf); + njt_free_chain(h2c->pool, cl); + } +} + +void +njt_http_v2_free_buffer(njt_connection_t *c, njt_http_v2_stream_buffer_t *qb) +{ + njt_http_v2_free_chain(c, qb->chain); + + qb->chain = NULL; +} + +ssize_t +njt_http_v2_stream_recv(njt_connection_t *c, u_char *buf, size_t size) +{ + ssize_t len; + njt_buf_t *b; + njt_chain_t *cl, *in; + njt_event_t *rev; + njt_http_v2_stream_t *stream; + njt_http_v2_connection_t *h2c; + + stream = c->stream; + h2c = stream->connection; + rev = c->read; + + if (c->error) { + njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, + "http2 stream id:0x%xL bad recv state", + stream->node->id); + return NJT_ERROR; + } + + njt_log_debug2(NJT_LOG_DEBUG_EVENT, c->log, 0, + "http2 stream id:0x%xL expected recv buf:%uz", + stream->node->id, size); + + if (size == 0) { + return 0; + } + + in = njt_http_v2_read_buffer(c, &stream->recv, size); + if (in == NJT_CHAIN_ERROR) { + return NJT_ERROR; + } + + len = 0; + + for (cl = in; cl; cl = cl->next) { + b = cl->buf; + len += b->last - b->pos; + buf = njt_cpymem(buf, b->pos, b->last - b->pos); + } + + njt_http_v2_free_chain(c, in); + + if (len == 0) { + rev->ready = 0; + + if (stream->in_closed) { + rev->eof = 1; + return 0; + } + + njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, + "http2 stream id:0x%xL recv() not ready", stream->node->id); + return NJT_AGAIN; + } + + if (njt_http_v2_send_window_update(stream->connection, + stream->node->id, len) + == NJT_ERROR) + { + stream->skip_data = 1; + return NJT_ERROR; + } + + h2c = stream->connection; + + if (!h2c->blocked) { + if (njt_http_v2_send_output_queue(h2c) == NJT_ERROR) { + stream->skip_data = 1; + return NJT_ERROR; + } + } + + stream->recv_window += len; + + njt_log_debug2(NJT_LOG_DEBUG_EVENT, c->log, 0, + "http2 stream id:0x%xL actual recv len:%z", stream->node->id, len); + + return len; +} + +ssize_t +njt_http_v2_stream_send(njt_connection_t *c, u_char *buf, size_t size) +{ + njt_log_error(NJT_LOG_ERR,c->log,0,"call njt_http_v2_stream_send error"); + return 0; +} + + +static void +ng_http_v2_copy_chain_data(njt_chain_t *dst_chain, njt_chain_t *src_chain) +{ + u_char *rpos, *wpos; + size_t data_size, buf_size, len; + njt_chain_t *src, *dst; + + src = src_chain; + dst = dst_chain; + + rpos = src->buf->pos; + wpos = dst->buf->last; + + while (src && dst) { + + data_size = src->buf->last - rpos; + buf_size = dst->buf->end - wpos; + + len = njt_min(data_size, buf_size); + + njt_memcpy(wpos, rpos, len); + + rpos += len; + wpos += len; + + if (rpos == src->buf->last) { + src = src->next; + if (src) { + rpos = src->buf->pos; + } + } + + if (wpos == dst->buf->end) { + dst = dst->next; + if (dst) { + wpos = dst->buf->last; + } + } + } +} + +ssize_t +njt_http_v2_recv_chain(njt_connection_t *fc, njt_chain_t *in, off_t limit) { + size_t len; + njt_buf_t *b; + njt_chain_t *cl, *out; + njt_event_t *rev; + njt_http_v2_stream_t *stream; + njt_http_v2_connection_t *h2c; + + stream = fc->stream; + h2c = stream->connection; + rev = fc->read; + + njt_log_debug1(NJT_LOG_DEBUG_EVENT, fc->log, 0, + "http2 stream id:0x%xL recv chain", stream->node->id); + + len = 0; + for (cl = in; cl; cl = cl->next) { + len += cl->buf->end - cl->buf->last; + } + + if (limit && len > (size_t) limit) { + len = limit; + } + + out = njt_http_v2_read_buffer(fc, &stream->recv, len); + if (out == NJT_CHAIN_ERROR) { + return NJT_ERROR; + } + + len = 0; + + for (cl = out; cl; cl = cl->next) { + b = cl->buf; + len += b->last - b->pos; + } + + if (len == 0) { + rev->ready = 0; + + if (stream->in_closed) { + rev->eof = 1; + return 0; + } + + njt_log_debug1(NJT_LOG_DEBUG_EVENT, fc->log, 0, + "http2 stream id:0x%xL recv chain() not ready", stream->node->id); + return NJT_AGAIN; + } + + ng_http_v2_copy_chain_data(in, out); + + njt_http_v2_free_chain(fc, out); + + njt_log_debug2(NJT_LOG_DEBUG_EVENT, fc->log, 0, + "http2 stream id:0x%xL actual recv chain len:%z", stream->node->id, len); + + if (njt_http_v2_send_window_update(stream->connection, + stream->node->id, len) + == NJT_ERROR) + { + stream->skip_data = 1; + return NJT_ERROR; + } + + h2c = stream->connection; + + if (!h2c->blocked) { + if (njt_http_v2_send_output_queue(h2c) == NJT_ERROR) { + stream->skip_data = 1; + return NJT_ERROR; + } + } + + stream->recv_window += len; + return len; + +} + +/*const int MAX_BUFF_SIZE = 1024*20; +const int MAX_RECV_SIZE = 5000; +const int MAX_RECV_4096 = 4096; + +njt_int_t +njt_http_v2_stream_buf_test_rand() +{ + njt_pool_t *pool; + njt_connection_t *conn; + njt_chain_t *cl, *in; + njt_http_v2_stream_t *stream; + njt_buf_t *b; + njt_http_v2_stream_buffer_t *qb; + njt_http_v2_connection_t *h2c; + + // 使用当前时间作为随机数种子 + srand(time(0)); + u_char wr[MAX_BUFF_SIZE+10]; + u_char rd[MAX_BUFF_SIZE+10]; + + pool = njt_create_pool(1024, njt_cycle->log); + if (pool == NULL) { + return 0; + } + + h2c = njt_pcalloc(pool, sizeof(njt_http_v2_connection_t)); + if (h2c == NULL) { + return 0; + } + + conn = njt_palloc(pool, sizeof(njt_connection_t)); + if (conn == NULL) { + return 0; + } + conn->pool = pool; + h2c->connection = conn; + conn->data = h2c; + h2c->pool = pool; + + + stream = njt_pcalloc(pool, sizeof(njt_http_v2_stream_t)); + if (stream == NULL) { + return 0; + } + + stream->connection = h2c; + stream->recv.last_chain = &stream->recv.chain; + conn->stream = stream; + stream->fc = conn; + qb = &stream->recv; + + size_t len, i = 0; + size_t recv_stat = 0, send_stat = 0 ,actual_read = 0, actual_write = 0; + size_t expect_read = 0, expecpt_write = 0; + + while (1) { + int w = rand() % 20 + 2; + actual_read = 0; + actual_write = 0; + expect_read = 0; + expecpt_write = 0; + while(w--) { + size_t size = (rand() % MAX_BUFF_SIZE) + 1; + char ch = 'a' + i++ % 26; + memset(wr,ch,size); + njt_http_v2_write_buffer(conn,wr,size); + send_stat += size; + actual_write += size; + expecpt_write += size; + } + + int r = rand() % 20 + 6; + while(r--) { + size_t size = rand() % MAX_BUFF_SIZE + 1; + + in = njt_http_v2_read_buffer(conn, &stream->recv, size); + if (in == NJT_CHAIN_ERROR) { + return 0; + } + + len = 0; + u_char *buf = rd; + for (cl = in; cl; cl = cl->next) { + b = cl->buf; + len += b->last - b->pos; + buf = njt_cpymem(buf, b->pos, b->last - b->pos); + } + + + recv_stat += len; + actual_read += len; + expect_read += size; + njt_http_v2_free_chain(conn, in); + + njt_chain_t *out = qb->chain; + len = 0; + while (out) { + cl = out; + if (!cl->buf->sync) { + len += cl->buf->last - cl->buf->pos; + } + out = out->next; + } + + if (len != qb->size) { + printf("qb->size:%ld, buf:%ld\n", qb->size, len); + return 0; + } + } + + printf("recv:%ld, send:%ld, buf:%ld, expecpt_write:%ld, actual_write:%ld, expecpt_read:%ld, actual_read:%ld\n", + recv_stat, send_stat, qb->size, expecpt_write, actual_write,expect_read,actual_read); + njt_http_v2_out_chain(qb->chain,0); + printf("last_chain:%p, last_chain**:%p\n", *qb->last_chain, qb->last_chain); + njt_http_v2_out_chain(*qb->last_chain,0); + printf("----------------------------------\n\n"); + + if (send_stat != recv_stat + qb->size) { + printf("recv:%ld, send:%ld, buf:%ld\n", recv_stat, send_stat, qb->size); + return 0; + } + + njt_chain_t *out = qb->chain; + len = 0; + while (out) { + cl = out; + if (!cl->buf->sync) { + len += cl->buf->last - cl->buf->pos; + } + out = out->next; + } + + if (len != qb->size) { + printf("qb->size:%ld, buf:%ld\n", qb->size, len); + return 0; + } + } + + return 0; +} + +njt_int_t +njt_http_v2_stream_buf_test_4096() +{ + njt_pool_t *pool; + njt_connection_t *conn; + njt_chain_t *cl, *in; + njt_http_v2_stream_t *stream; + njt_buf_t *b; + njt_http_v2_stream_buffer_t *qb; + njt_http_v2_connection_t *h2c; + + // 使用当前时间作为随机数种子 + srand(time(0)); + u_char wr[MAX_BUFF_SIZE+10]; + u_char rd[MAX_BUFF_SIZE+10]; + + pool = njt_create_pool(1024, njt_cycle->log); + if (pool == NULL) { + return 0; + } + + h2c = njt_pcalloc(pool, sizeof(njt_http_v2_connection_t)); + if (h2c == NULL) { + return 0; + } + + + conn = njt_palloc(pool, sizeof(njt_connection_t)); + if (conn == NULL) { + return 0; + } + conn->pool = pool; + h2c->connection = conn; + conn->data = h2c; + h2c->pool = pool; + + + stream = njt_pcalloc(pool, sizeof(njt_http_v2_stream_t)); + if (stream == NULL) { + return 0; + } + + stream->connection = h2c; + stream->recv.last_chain = &stream->recv.chain; + conn->stream = stream; + stream->fc = conn; + qb = &stream->recv; + + size_t len, i = 0; + size_t recv_stat = 0, send_stat = 0 ,actual_read = 0, actual_write = 0; + size_t expect_read = 0, expecpt_write = 0; + + while (1) { + int w = rand() % 20 + 2; + actual_read = 0; + actual_write = 0; + expect_read = 0; + expecpt_write = 0; + while(w--) { + size_t size = MAX_RECV_4096; + char ch = 'a' + i++ % 26; + memset(wr,ch,size); + njt_http_v2_write_buffer(conn,wr,size); + send_stat += size; + actual_write += size; + expecpt_write += size; + } + + int r = rand() % 20 + 6; + while(r--) { + size_t size = MAX_RECV_4096; + + in = njt_http_v2_read_buffer(conn, &stream->recv, size); + if (in == NJT_CHAIN_ERROR) { + return 0; + } + + len = 0; + u_char *buf = rd; + for (cl = in; cl; cl = cl->next) { + b = cl->buf; + len += b->last - b->pos; + buf = njt_cpymem(buf, b->pos, b->last - b->pos); + } + + + recv_stat += len; + actual_read += len; + expect_read += size; + njt_http_v2_free_chain(conn, in); + + njt_chain_t *out = qb->chain; + len = 0; + while (out) { + cl = out; + if (!cl->buf->sync) { + len += cl->buf->last - cl->buf->pos; + } + out = out->next; + } + + if (len != qb->size) { + printf("qb->size:%ld, buf:%ld\n", qb->size, len); + return 0; + } + } + + printf("recv:%ld, send:%ld, buf:%ld, expecpt_write:%ld, actual_write:%ld, expecpt_read:%ld, actual_read:%ld\n", + recv_stat, send_stat, qb->size, expecpt_write, actual_write,expect_read,actual_read); + njt_http_v2_out_chain(qb->chain,0); + printf("last_chain:%p, last_chain**:%p\n", *qb->last_chain, qb->last_chain); + njt_http_v2_out_chain(*qb->last_chain,0); + printf("----------------------------------\n\n"); + + if (send_stat != recv_stat + qb->size) { + printf("recv:%ld, send:%ld, buf:%ld\n", recv_stat, send_stat, qb->size); + return 0; + } + + njt_chain_t *out = qb->chain; + len = 0; + while (out) { + cl = out; + if (!cl->buf->sync) { + len += cl->buf->last - cl->buf->pos; + } + out = out->next; + } + + if (len != qb->size) { + printf("qb->size:%ld, buf:%ld\n", qb->size, len); + return 0; + } + } + + return 0; +} + +njt_int_t +njt_http_v2_stream_buf_test() { + njt_http_v2_stream_buf_test_4096(); + njt_http_v2_stream_buf_test_rand(); + return 0; +} +*/ \ No newline at end of file diff --git a/src/http/v2/njt_http_v2_stream.h b/src/http/v2/njt_http_v2_stream.h new file mode 100644 index 00000000..126d09ae --- /dev/null +++ b/src/http/v2/njt_http_v2_stream.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2024 Kern + */ + +#ifndef _NJT_HTTP_V2_STREAM_H_INCLUDED_ +#define _NJT_HTTP_V2_STREAM_H_INCLUDED_ + +#include +#include +#include + +typedef struct { + uint64_t size; + njt_chain_t *chain; + njt_chain_t **last_chain; +} njt_http_v2_stream_buffer_t; + +#define njt_http_v2_get_connection(c) \ + (((c)->stream) ? (((njt_http_v2_stream_t *)((c)->stream))->connection) : NULL) + +ssize_t njt_http_v2_stream_send(njt_connection_t *c, u_char *buf, size_t size); +ssize_t njt_http_v2_stream_recv(njt_connection_t *c, u_char *buf, size_t size); +njt_chain_t *njt_http_v2_write_chain(njt_connection_t *c, njt_http_v2_stream_buffer_t *qb, + njt_chain_t *in, uint64_t limit); +njt_int_t njt_http_v2_write_buffer(njt_connection_t *c, u_char *data,size_t len); +ssize_t njt_http_v2_recv_chain(njt_connection_t *fc, njt_chain_t *in, off_t limit); + +#endif \ No newline at end of file diff --git a/src/http/v2/njt_http_v2_table.c b/src/http/v2/njt_http_v2_table.c index 1fcb92a3..c78b6acb 100755 --- a/src/http/v2/njt_http_v2_table.c +++ b/src/http/v2/njt_http_v2_table.c @@ -349,7 +349,9 @@ njt_http_v2_table_size(njt_http_v2_connection_t *h2c, size_t size) njt_log_debug2(NJT_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 new hpack table size: %uz was:%uz", size, h2c->hpack.size); - + if (h2c->fake) { + return NJT_ERROR; + } needed = h2c->hpack.size - size; while (needed > (ssize_t) h2c->hpack.free) { -- Gitee From fc2799dfb9fd2ed0e2650650140ef6e77dec37d6 Mon Sep 17 00:00:00 2001 From: kern Date: Tue, 23 Jul 2024 20:05:51 +0800 Subject: [PATCH 2/4] add http3 proxy --- src/event/quic/njt_event_quic.c | 607 ++++- src/event/quic/njt_event_quic.h | 11 + src/event/quic/njt_event_quic_ack.c | 18 +- src/event/quic/njt_event_quic_ack.h | 2 + src/event/quic/njt_event_quic_connection.h | 32 +- src/event/quic/njt_event_quic_connid.c | 63 +- src/event/quic/njt_event_quic_connid.h | 7 +- src/event/quic/njt_event_quic_migration.c | 11 +- .../quic/njt_event_quic_openssl_compat.c | 5 + src/event/quic/njt_event_quic_output.c | 84 +- src/event/quic/njt_event_quic_protection.c | 101 +- src/event/quic/njt_event_quic_protection.h | 4 +- src/event/quic/njt_event_quic_socket.c | 70 +- src/event/quic/njt_event_quic_socket.h | 3 + src/event/quic/njt_event_quic_ssl.c | 171 +- src/event/quic/njt_event_quic_ssl.h | 3 + src/event/quic/njt_event_quic_streams.c | 463 +++- src/event/quic/njt_event_quic_streams.h | 3 + src/event/quic/njt_event_quic_tokens.c | 47 + src/event/quic/njt_event_quic_tokens.h | 9 + src/event/quic/njt_event_quic_transport.c | 210 +- src/event/quic/njt_event_quic_transport.h | 7 +- src/http/modules/njt_http_proxy_module.c | 2184 ++++++++++++++++- src/http/modules/njt_http_proxy_module.h | 10 + .../njt_http_upstream_keepalive_module.c | 24 + src/http/njt_http_request.h | 1 + src/http/njt_http_upstream.c | 575 ++++- src/http/njt_http_upstream.h | 5 + src/http/v2/njt_http_v2.c | 2 +- src/http/v2/njt_http_v2_stream.c | 2 +- src/http/v3/njt_http_v3.h | 26 + src/http/v3/njt_http_v3_module.c | 38 + src/http/v3/njt_http_v3_parse.c | 33 +- src/http/v3/njt_http_v3_request.c | 21 + src/http/v3/njt_http_v3_uni.c | 44 +- 35 files changed, 4556 insertions(+), 340 deletions(-) diff --git a/src/event/quic/njt_event_quic.c b/src/event/quic/njt_event_quic.c index 9a3dfae5..a128dd51 100644 --- a/src/event/quic/njt_event_quic.c +++ b/src/event/quic/njt_event_quic.c @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -18,6 +19,11 @@ static njt_int_t njt_quic_handle_stateless_reset(njt_connection_t *c, static void njt_quic_input_handler(njt_event_t *rev); static void njt_quic_close_handler(njt_event_t *ev); +static void njt_quic_dummy_handler(njt_event_t *ev); +static void njt_quic_client_input_handler(njt_event_t *rev); +static njt_int_t njt_quic_client_start(njt_connection_t *c, + njt_quic_header_t *pkt); + static njt_int_t njt_quic_handle_datagram(njt_connection_t *c, njt_buf_t *b, njt_quic_conf_t *conf); static njt_int_t njt_quic_handle_packet(njt_connection_t *c, @@ -185,11 +191,19 @@ njt_quic_apply_transport_params(njt_connection_t *c, njt_quic_tp_t *ctp) qc->tp.max_idle_timeout = ctp->max_idle_timeout; } - qc->streams.server_max_streams_bidi = ctp->initial_max_streams_bidi; - qc->streams.server_max_streams_uni = ctp->initial_max_streams_uni; + qc->streams.server.bidi.max = ctp->initial_max_streams_bidi; + qc->streams.server.uni.max = ctp->initial_max_streams_uni; + + if (qc->client) { + njt_memcpy(qc->path->cid->sr_token, + ctp->sr_token, NJT_QUIC_SR_TOKEN_LEN); + } njt_memcpy(&qc->ctp, ctp, sizeof(njt_quic_tp_t)); + /* apply transport parameters to early created streams */ + njt_quic_streams_init_state(c); + return NJT_OK; } @@ -222,10 +236,339 @@ njt_quic_run(njt_connection_t *c, njt_quic_conf_t *conf) } +static void +njt_quic_dummy_handler(njt_event_t *ev) +{ +} + + +njt_int_t +njt_quic_create_client(njt_quic_conf_t *conf, njt_connection_t *c) +{ + int value; + njt_log_t *log; + njt_quic_connection_t *qc; + +#if (NJT_HAVE_IP_MTU_DISCOVER) + + if (c->sockaddr->sa_family == AF_INET) { + value = IP_PMTUDISC_DO; + + if (setsockopt(c->fd, IPPROTO_IP, IP_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + njt_log_error(NJT_LOG_ALERT, c->log, njt_socket_errno, + "setsockopt(IP_MTU_DISCOVER) " + "for quic conn failed, ignored"); + } + } + +#elif (NJT_HAVE_IP_DONTFRAG) + + if (c->sockaddr->sa_family == AF_INET) { + value = 1; + + if (setsockopt(c->fd, IPPROTO_IP, IP_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + njt_log_error(NJT_LOG_ALERT, c->log, njt_socket_errno, + "setsockopt(IP_DONTFRAG) " + "for quic conn failed, ignored"); + } + } + +#endif + +#if (NJT_HAVE_INET6) + +#if (NJT_HAVE_IPV6_MTU_DISCOVER) + + if (c->sockaddr->sa_family == AF_INET6) { + value = IPV6_PMTUDISC_DO; + + if (setsockopt(c->fd, IPPROTO_IPV6, IPV6_MTU_DISCOVER, + (const void *) &value, sizeof(int)) + == -1) + { + njt_log_error(NJT_LOG_ALERT, c->log, njt_socket_errno, + "setsockopt(IPV6_MTU_DISCOVER) " + "for quic conn failed, ignored"); + } + } + +#elif (NJT_HAVE_IP_DONTFRAG) + + if (c->sockaddr->sa_family == AF_INET6) { + + value = 1; + + if (setsockopt(c->fd, IPPROTO_IPV6, IPV6_DONTFRAG, + (const void *) &value, sizeof(int)) + == -1) + { + njt_log_error(NJT_LOG_ALERT, c->log, njt_socket_errno, + "setsockopt(IPV6_DONTFRAG) " + "for quic conn failed, ignored"); + } + } +#endif + +#endif + + c->read->handler = njt_quic_client_input_handler; + c->write->handler = njt_quic_dummy_handler; + + if (conf->active_connection_id_limit == 0) { + /* + * TODO: remove when done with testing + * + * this case exists purely for testing/coverage purposes; + * (RFC requires minimum value of 2, and default is 2, so no real + * configurations want to set this zero) + */ + return NJT_ERROR; + } + + /* + * each stream calls c->listening()->handler for initialization; + * handler is set by the caller + */ + c->listening = njt_pcalloc(c->pool, sizeof(njt_listening_t)); + if (c->listening == NULL) { + return NJT_ERROR; + } + + /* + * 'c' is a new connection to upstream and c->log is inherited from + * the r->connection->log (allocated from r->pool) + * + * main quic connection (this) may exist longer than client connection + * due to keepalive and/or non-immediate closing + * + * unlike tcp keepalive, main quic connection is alive during the + * time between requests, and may produce events with logging. + * + * so, use log from njt_cycle instead of client log, which may be + * destroyed. + */ + log = njt_palloc(c->pool, sizeof(njt_log_t)); + if (log == NULL) { + return NJT_ERROR; + } + + *log = *njt_cycle->log; + c->log = log; + + log->connection = c->number; + + c->read->log = c->log; + c->write->log = c->log; + c->pool->log = c->log; + + qc = njt_quic_new_connection(c, conf, NULL); + if (qc == NULL) { + return NJT_ERROR; + } + + njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic client initialized on c:%p", c); + + return NJT_OK; +} + + +void +njt_quic_client_set_ssl_data(njt_connection_t *c, void *data) +{ + njt_quic_connection_t *qc; + + qc = njt_quic_get_connection(c); + + if (qc == NULL) { + return; + } + + qc->init_ssl_data = data; +} + + +void * +njt_quic_client_get_ssl_data(njt_connection_t *c) +{ + njt_quic_connection_t *qc; + + qc = njt_quic_get_connection(c); + + if (qc == NULL) { + return NULL; + } + + return qc->init_ssl_data; +} + + +njt_int_t +njt_quic_connect(njt_connection_t *c, njt_quic_init_ssl_pt init_ssl, void *data) +{ + njt_str_t id; + njt_quic_client_id_t *cid; + njt_quic_connection_t *qc; + + qc = njt_quic_get_connection(c); + + qc->init_ssl = init_ssl; + qc->init_ssl_data = data; + + qc->ctp.max_udp_payload_size = NJT_QUIC_MAX_UDP_PAYLOAD_SIZE; + + /* use initial dcid we generated on start */ + id.data = qc->incid; + id.len = NJT_QUIC_SERVER_CID_LEN; + + cid = njt_quic_create_client_id(c, &id, 0, NULL); + if (cid == NULL) { + return NJT_ERROR; + } + + qc->path = njt_quic_new_path(c, c->sockaddr, c->socklen, cid); + if (qc->path == NULL) { + return NJT_ERROR; + } + + qc->path->tag = NJT_QUIC_PATH_ACTIVE; + njt_quic_path_dbg(c, "set active", qc->path); + + njt_log_debug0(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic client initiating connection"); + + return njt_quic_client_start(c, NULL); +} + + +static njt_int_t +njt_quic_client_start(njt_connection_t *c, njt_quic_header_t *pkt) +{ + njt_str_t dcid; + njt_queue_t *q; + njt_quic_frame_t *f; + njt_quic_send_ctx_t *ctx; + njt_quic_connection_t *qc; + + qc = njt_quic_get_connection(c); + + if (pkt == NULL) { + /* not a retry packet */ + goto start; + } + + if (pkt->token.len <= 16) { + /* + * A client MUST discard a Retry packet with a zero-length + * Retry Token field. + */ + + njt_log_error(NJT_LOG_INFO, c->log, 0, + "quic client bad token length"); + return NJT_ERROR; + } + + if (njt_quic_verify_retry_token_integrity(c, pkt) != NJT_OK) { + /* + * A client MUST accept and process at most one Retry packet + * for each connection attempt. + */ + + njt_log_error(NJT_LOG_INFO, c->log, 0, + "quic client retry token integrity check failed"); + return NJT_ERROR; + } + + /* server responded with new id, update */ + njt_memcpy(qc->path->cid->id, pkt->scid.data, pkt->scid.len); + qc->path->cid->len = pkt->scid.len; + qc->server_id_known = 1; + + qc->client_retry.len = pkt->token.len - 16; + + qc->client_retry.data = njt_pnalloc(c->pool, + qc->client_retry.len); + if (qc->client_retry.data == NULL) { + return NJT_ERROR; + } + + njt_memcpy(qc->client_retry.data, pkt->token.data, + qc->client_retry.len); + + /* prepare for one more SID change later */ + qc->server_id_known = 0; + + /* + * RFC 9002 6.3 Handling Retry Packets + * + * Clients that receive a Retry packet reset congestion control and loss + * recovery state, including resetting any pending timers. Other connection + * state, in particular cryptographic handshake messages, is retained; see + * Section 17.2.5 of [QUIC-TRANSPORT]. + */ + + ctx = njt_quic_get_send_ctx(qc, ssl_encryption_initial); + + while (!njt_queue_empty(&ctx->sent)) { + q = njt_queue_head(&ctx->sent); + njt_queue_remove(q); + + f = njt_queue_data(q, njt_quic_frame_t, queue); + njt_quic_congestion_ack(c, f); + njt_quic_free_frame(c, f); + } + + ctx->send_ack = 0; + qc->pto_count = 0; + + njt_quic_congestion_reset(qc); + + if (qc->pto.timer_set) { + njt_del_timer(&qc->pto); + } + + njt_quic_set_lost_timer(c); + + /* now we need to restart handshake from the beginning */ + + /* reset offset in CRYPTO frames */ + ctx->crypto_sent = 0; + + /* since SID has changed, new keys need to be generated */ + dcid.data = pkt->scid.data; + dcid.len = pkt->scid.len; + + njt_quic_keys_cleanup(qc->keys); + + if (njt_quic_keys_set_initial_secret(qc->keys, &dcid, c->log) != NJT_OK) { + return NJT_ERROR; + } + +start: + + if (njt_quic_client_handshake(c) != NJT_OK) { + qc->error_reason = "handshake failed"; + return NJT_ERROR; + } + + njt_log_debug0(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic client handshake started"); + + return NJT_AGAIN; +} + + static njt_quic_connection_t * njt_quic_new_connection(njt_connection_t *c, njt_quic_conf_t *conf, njt_quic_header_t *pkt) { + njt_str_t dcid; njt_uint_t i; njt_quic_tp_t *ctp; njt_quic_connection_t *qc; @@ -235,12 +578,18 @@ njt_quic_new_connection(njt_connection_t *c, njt_quic_conf_t *conf, return NULL; } + /* server connection requires a packet from client */ + qc->client = (pkt == NULL); + qc->keys = njt_pcalloc(c->pool, sizeof(njt_quic_keys_t)); if (qc->keys == NULL) { return NULL; } - qc->version = pkt->version; + qc->keys->client = qc->client; + + /* client always initiates QUIC v.1 */ + qc->version = qc->client ? 1 : pkt->version; njt_rbtree_init(&qc->streams.tree, &qc->streams.sentinel, njt_quic_rbtree_insert_stream); @@ -303,15 +652,12 @@ njt_quic_new_connection(njt_connection_t *c, njt_quic_conf_t *conf, qc->streams.recv_max_data = qc->tp.initial_max_data; qc->streams.recv_window = qc->streams.recv_max_data; - qc->streams.client_max_streams_uni = qc->tp.initial_max_streams_uni; - qc->streams.client_max_streams_bidi = qc->tp.initial_max_streams_bidi; + qc->streams.client.uni.max = qc->tp.initial_max_streams_uni; + qc->streams.client.bidi.max = qc->tp.initial_max_streams_bidi; - qc->congestion.window = njt_min(10 * qc->tp.max_udp_payload_size, - njt_max(2 * qc->tp.max_udp_payload_size, - 14720)); - qc->congestion.ssthresh = (size_t) -1; - qc->congestion.recovery_start = njt_current_msec; + njt_quic_congestion_reset(qc); + if (!qc->client) { if (pkt->validated && pkt->retried) { qc->tp.retry_scid.len = pkt->dcid.len; qc->tp.retry_scid.data = njt_pstrdup(c->pool, &pkt->dcid); @@ -319,14 +665,29 @@ njt_quic_new_connection(njt_connection_t *c, njt_quic_conf_t *conf, return NULL; } } + } + + if (qc->client) { + if (njt_quic_create_server_id(c, qc->incid, 1) != NJT_OK) { + return NULL; + } + + dcid.data = qc->incid; + dcid.len = NJT_QUIC_SERVER_CID_LEN; + + } else { + dcid = pkt->dcid; + } - if (njt_quic_keys_set_initial_secret(qc->keys, &pkt->dcid, c->log) + if (njt_quic_keys_set_initial_secret(qc->keys, &dcid, c->log) != NJT_OK) { return NULL; } + if (!qc->client) { qc->validated = pkt->validated; + } if (njt_quic_open_sockets(c, qc, pkt) != NJT_OK) { njt_quic_keys_cleanup(qc->keys); @@ -454,10 +815,151 @@ njt_quic_input_handler(njt_event_t *rev) } +static void +njt_quic_client_input_handler(njt_event_t *rev) +{ + njt_int_t rc; + njt_str_t key; + njt_buf_t *b; + njt_connection_t *c; + njt_quic_socket_t *qsock; + njt_quic_connection_t *qc; + + ssize_t n; + njt_buf_t dbuf; + static u_char cbuf[65535]; + + njt_log_debug0(NJT_LOG_DEBUG_EVENT, rev->log, 0, + "quic client input handler"); + + c = rev->data; + qc = njt_quic_get_connection(c); + + c->log->action = "handling quic client input"; + + if (rev->timedout) { + njt_log_error(NJT_LOG_INFO, c->log, NJT_ETIMEDOUT, + "quic server timed out"); + njt_quic_close_connection(c, NJT_DONE); + return; + } + + if (c->close) { + c->close = 0; + + if (!njt_exiting) { + qc->error = NJT_QUIC_ERR_NO_ERROR; + qc->error_reason = "graceful shutdown"; + njt_quic_close_connection(c, NJT_ERROR); + return; + } + + if (!qc->closing && qc->conf->shutdown) { + qc->conf->shutdown(c); + } + + return; + } + + if (!rev->ready) { + if (qc->closing) { + njt_quic_close_connection(c, NJT_OK); + + } else if (qc->shutdown) { + njt_quic_shutdown_quic(c); + } + + return; + } + + for ( ;; ) { + + njt_memzero(&dbuf, sizeof(njt_buf_t)); + + n = c->recv(c, cbuf, sizeof(cbuf)); + + njt_log_debug2(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic njt_quic_input_handler recv: fd:%d %z", + c->fd, n); + + if (n == NJT_ERROR) { + qc->error_reason = "failed read"; + njt_quic_close_connection(c, NJT_ERROR); + return; + } + + if (n == NJT_AGAIN) { + break; + } + + /* + * actually, since client uses connected UDP socket, there should + * be no different addresses of incoming packets; + * + * we only need to dispatch between different quic sockets, + * as client may use different DCIDs + */ + + if (njt_quic_get_packet_dcid(c->log, cbuf, n, &key) != NJT_OK) { + /* broken packet, ignore */ + continue; + } + + qsock = njt_quic_find_socket_by_id(c, &key); + if (qsock == NULL) { + /* client uses unknown dcid, ignore */ + continue; + } + + c->udp = &qsock->udp; + + qsock = njt_quic_get_socket(c); + + njt_memcpy(&qsock->sockaddr, c->sockaddr, c->socklen); + qsock->socklen = c->socklen; + + dbuf.pos = cbuf; + dbuf.last = cbuf + n; + dbuf.start = dbuf.pos; + dbuf.end = cbuf + sizeof(cbuf); + + c->udp->buffer = &dbuf; + + b = c->udp->buffer; + + rc = njt_quic_handle_datagram(c, b, NULL); + + if (rc == NJT_ERROR) { + njt_quic_close_connection(c, NJT_ERROR); + return; + } + + if (rc == NJT_DECLINED) { + continue; + } + + /* rc == NJT_OK */ + } + + if (njt_handle_read_event(rev, 0) != NJT_OK) { + njt_quic_close_connection(c, NJT_ERROR); + return; + } + + qc->send_timer_set = 0; + njt_add_timer(rev, qc->tp.max_idle_timeout); + + njt_quic_connstate_dbg(c); +} + + void njt_quic_close_connection(njt_connection_t *c, njt_int_t rc) { njt_uint_t i; +#if (NJT_STAT_STUB) + njt_uint_t is_client; +#endif njt_pool_t *pool; njt_quic_send_ctx_t *ctx; njt_quic_connection_t *qc; @@ -467,9 +969,16 @@ njt_quic_close_connection(njt_connection_t *c, njt_int_t rc) if (qc == NULL) { njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, "quic packet rejected rc:%i, cleanup connection", rc); +#if (NJT_STAT_STUB) + is_client = 0; +#endif goto quic_done; } +#if (NJT_STAT_STUB) + is_client = qc->client; +#endif + njt_log_debug2(NJT_LOG_DEBUG_EVENT, c->log, 0, "quic close %s rc:%i", qc->closing ? "resumed": "initiated", rc); @@ -547,6 +1056,8 @@ njt_quic_close_connection(njt_connection_t *c, njt_int_t rc) } if (njt_quic_close_streams(c, qc) == NJT_AGAIN) { + njt_log_debug0(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic close: waiting for streams"); return; } @@ -571,6 +1082,8 @@ njt_quic_close_connection(njt_connection_t *c, njt_int_t rc) } if (qc->close.timer_set) { + njt_log_debug0(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic close: waiting for timers"); return; } @@ -598,7 +1111,9 @@ quic_done: } #if (NJT_STAT_STUB) - (void) njt_atomic_fetch_add(njt_stat_active, -1); + if (!is_client) { + (void) njt_atomic_fetch_add(njt_stat_active, -1); + } #endif c->destroyed = 1; @@ -623,11 +1138,32 @@ njt_quic_finalize_connection(njt_connection_t *c, njt_uint_t err, return; } - qc->error = err; - qc->error_reason = reason; - qc->error_app = 1; qc->error_ftype = 0; + /* 10.2.3. Immediate Close during the Handshake + * + * Sending a CONNECTION_CLOSE of type 0x1d in an Initial or Handshake + * packet could expose application state or be used to alter application + * state. A CONNECTION_CLOSE of type 0x1d MUST be replaced by a + * CONNECTION_CLOSE of type 0x1c when sending the frame in Initial or + * Handshake packets. + * + * Endpoints MUST clear the value of the Reason Phrase field and SHOULD use + * the APPLICATION_ERROR code when converting to a CONNECTION_CLOSE of type + * 0x1c. + */ + + if (c->ssl == NULL || !c->ssl->handshaked) { + qc->error = NJT_QUIC_ERR_APPLICATION_ERROR; + qc->error_reason = ""; + qc->error_app = 0; + + } else { + qc->error = err; + qc->error_reason = reason; + qc->error_app = 1; + } + njt_post_event(&qc->close, &njt_posted_events); } @@ -667,11 +1203,14 @@ njt_quic_handle_datagram(njt_connection_t *c, njt_buf_t *b, size_t size; u_char *p, *start; njt_int_t rc; - njt_uint_t good; + njt_uint_t good, is_server_packet; njt_quic_path_t *path; njt_quic_header_t pkt; njt_quic_connection_t *qc; + qc = njt_quic_get_connection(c); + is_server_packet = qc ? qc->client : 0; + good = 0; path = NULL; @@ -690,6 +1229,7 @@ njt_quic_handle_datagram(njt_connection_t *c, njt_buf_t *b, pkt.path = path; pkt.flags = p[0]; pkt.raw->pos++; + pkt.server = is_server_packet; rc = njt_quic_handle_packet(c, conf, &pkt); @@ -835,10 +1375,18 @@ njt_quic_handle_packet(njt_connection_t *c, njt_quic_conf_t *conf, } } - if (njt_quic_check_csid(qc, pkt) != NJT_OK) { - return NJT_DECLINED; - } + if (qc->client) { + + if (njt_quic_pkt_retry(pkt->flags)) { + return njt_quic_client_start(c, pkt); + } + } else { + + if (njt_quic_check_csid(qc, pkt) != NJT_OK) { + return NJT_DECLINED; + } + } } rc = njt_quic_handle_payload(c, pkt); @@ -991,6 +1539,14 @@ njt_quic_handle_payload(njt_connection_t *c, njt_quic_header_t *pkt) c->log->action = "handling decrypted packet"; + if (qc->client && !qc->server_id_known) { + /* server generated new ID, use it (only if decrypted) */ + + njt_memcpy(qc->path->cid->id, pkt->scid.data, pkt->scid.len); + qc->path->cid->len = pkt->scid.len; + qc->server_id_known = 1; + } + if (pkt->path == NULL) { rc = njt_quic_set_path(c, pkt); if (rc != NJT_OK) { @@ -1378,6 +1934,19 @@ njt_quic_handle_frames(njt_connection_t *c, njt_quic_header_t *pkt) break; + case NJT_QUIC_FT_HANDSHAKE_DONE: + njt_quic_streams_notify_write(c); + break; + + case NJT_QUIC_FT_NEW_TOKEN: + + if (njt_quic_handle_new_token_frame(c, &frame.u.token) + != NJT_OK) + { + return NJT_ERROR; + } + break; + default: njt_log_debug0(NJT_LOG_DEBUG_EVENT, c->log, 0, "quic missing frame handler"); diff --git a/src/event/quic/njt_event_quic.h b/src/event/quic/njt_event_quic.h index c2fb86cd..caf5fb8c 100644 --- a/src/event/quic/njt_event_quic.h +++ b/src/event/quic/njt_event_quic.h @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -32,6 +33,8 @@ typedef njt_int_t (*njt_quic_init_pt)(njt_connection_t *c); typedef void (*njt_quic_shutdown_pt)(njt_connection_t *c); +typedef njt_int_t (*njt_quic_init_ssl_pt)(njt_connection_t *c, void *data); + typedef enum { NJT_QUIC_STREAM_SEND_READY = 0, @@ -84,6 +87,7 @@ typedef struct { u_char av_token_key[NJT_QUIC_AV_KEY_LEN]; u_char sr_token_key[NJT_QUIC_SR_KEY_LEN]; + njt_str_t alpn; } njt_quic_conf_t; @@ -114,6 +118,13 @@ struct njt_quic_stream_s { void njt_quic_recvmsg(njt_event_t *ev); void njt_quic_run(njt_connection_t *c, njt_quic_conf_t *conf); + +njt_int_t njt_quic_create_client(njt_quic_conf_t *conf, njt_connection_t *c); +njt_int_t njt_quic_connect(njt_connection_t *c, njt_quic_init_ssl_pt init_ssl, + void *data); +void njt_quic_client_set_ssl_data(njt_connection_t *c, void *data); +void *njt_quic_client_get_ssl_data(njt_connection_t *c); + njt_connection_t *njt_quic_open_stream(njt_connection_t *c, njt_uint_t bidi); void njt_quic_finalize_connection(njt_connection_t *c, njt_uint_t err, const char *reason); diff --git a/src/event/quic/njt_event_quic_ack.c b/src/event/quic/njt_event_quic_ack.c index 570c47fd..08861bba 100644 --- a/src/event/quic/njt_event_quic_ack.c +++ b/src/event/quic/njt_event_quic_ack.c @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -375,6 +376,19 @@ done: } +void +njt_quic_congestion_reset(njt_quic_connection_t *qc) +{ + njt_memzero(&qc->congestion, sizeof(njt_quic_congestion_t)); + + qc->congestion.window = njt_min(10 * qc->tp.max_udp_payload_size, + njt_max(2 * qc->tp.max_udp_payload_size, + 14720)); + qc->congestion.ssthresh = (size_t) -1; + qc->congestion.recovery_start = njt_current_msec; +} + + static void njt_quic_drop_ack_ranges(njt_connection_t *c, njt_quic_send_ctx_t *ctx, uint64_t pn) @@ -612,8 +626,8 @@ njt_quic_resend_frames(njt_connection_t *c, njt_quic_send_ctx_t *ctx) case NJT_QUIC_FT_MAX_STREAMS: case NJT_QUIC_FT_MAX_STREAMS2: f->u.max_streams.limit = f->u.max_streams.bidi - ? qc->streams.client_max_streams_bidi - : qc->streams.client_max_streams_uni; + ? qc->streams.client.bidi.max + : qc->streams.client.uni.max; njt_quic_queue_frame(qc, f); break; diff --git a/src/event/quic/njt_event_quic_ack.h b/src/event/quic/njt_event_quic_ack.h index fb5380e6..625e3c39 100644 --- a/src/event/quic/njt_event_quic_ack.h +++ b/src/event/quic/njt_event_quic_ack.h @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -18,6 +19,7 @@ njt_int_t njt_quic_handle_ack_frame(njt_connection_t *c, void njt_quic_congestion_ack(njt_connection_t *c, njt_quic_frame_t *frame); +void njt_quic_congestion_reset(njt_quic_connection_t *qc); void njt_quic_resend_frames(njt_connection_t *c, njt_quic_send_ctx_t *ctx); void njt_quic_set_lost_timer(njt_connection_t *c); void njt_quic_pto_handler(njt_event_t *ev); diff --git a/src/event/quic/njt_event_quic_connection.h b/src/event/quic/njt_event_quic_connection.h index 311396b2..42ccaa6e 100644 --- a/src/event/quic/njt_event_quic_connection.h +++ b/src/event/quic/njt_event_quic_connection.h @@ -1,6 +1,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -133,6 +134,16 @@ struct njt_quic_socket_s { njt_uint_t used; /* unsigned used:1; */ }; +typedef struct { + uint64_t max; + uint64_t count; +} njt_quic_stream_ctl_t; + + +typedef struct { + njt_quic_stream_ctl_t uni; + njt_quic_stream_ctl_t bidi; +} njt_quic_stream_peer_t; typedef struct { njt_rbtree_t tree; @@ -149,15 +160,8 @@ typedef struct { uint64_t send_offset; uint64_t send_max_data; - uint64_t server_max_streams_uni; - uint64_t server_max_streams_bidi; - uint64_t server_streams_uni; - uint64_t server_streams_bidi; - - uint64_t client_max_streams_uni; - uint64_t client_max_streams_bidi; - uint64_t client_streams_uni; - uint64_t client_streams_bidi; + njt_quic_stream_peer_t server; + njt_quic_stream_peer_t client; njt_uint_t initialized; /* unsigned initialized:1; */ @@ -278,6 +282,13 @@ struct njt_quic_connection_s { njt_uint_t shutdown_code; const char *shutdown_reason; + u_char incid[NJT_QUIC_SERVER_CID_LEN]; + + njt_str_t client_retry; + njt_str_t client_new_token; + njt_quic_init_ssl_pt init_ssl; + void *init_ssl_data; + unsigned error_app:1; unsigned send_timer_set:1; unsigned closing:1; @@ -286,6 +297,9 @@ struct njt_quic_connection_s { unsigned key_phase:1; unsigned validated:1; unsigned client_tp_done:1; + unsigned server_id_known:1; + unsigned client:1; + unsigned switch_keys:1; }; diff --git a/src/event/quic/njt_event_quic_connid.c b/src/event/quic/njt_event_quic_connid.c index 215992d0..48acf5da 100644 --- a/src/event/quic/njt_event_quic_connid.c +++ b/src/event/quic/njt_event_quic_connid.c @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -25,12 +26,16 @@ static njt_int_t njt_quic_send_server_id(njt_connection_t *c, njt_int_t -njt_quic_create_server_id(njt_connection_t *c, u_char *id) +njt_quic_create_server_id(njt_connection_t *c, u_char *id, njt_uint_t client) { if (RAND_bytes(id, NJT_QUIC_SERVER_CID_LEN) != 1) { return NJT_ERROR; } + if (client) { + return NJT_OK; + } + #if (NJT_QUIC_BPF) if (njt_quic_bpf_attach_id(c, id) != NJT_OK) { njt_log_error(NJT_LOG_ERR, c->log, 0, @@ -203,6 +208,62 @@ done: return NJT_OK; } +njt_int_t +njt_quic_handle_new_token_frame(njt_connection_t *c, + njt_quic_new_token_frame_t *f) +{ + u_char *p; + njt_quic_connection_t *qc; + + qc = njt_quic_get_connection(c); + + if (f->length == 0) { + + /* + * A client MUST treat receipt of a NEW_TOKEN frame with an empty + * Token field as a connection error of type FRAME_ENCODING_ERROR. + */ + qc->error = NJT_QUIC_ERR_FRAME_ENCODING_ERROR; + qc->error_reason = "zero length NEW_TOKEN frame"; + + njt_log_error(NJT_LOG_ERR, c->log, njt_socket_errno, + "quic NEW_TOKEN frame of zero length"); + return NJT_ERROR; + } + + if (f->length > NJT_QUIC_MAX_NEW_TOKEN) { + njt_log_error(NJT_LOG_ERR, c->log, njt_socket_errno, + "quic NEW_TOKEN frame is too big: %ui", f->length); + return NJT_ERROR; + } + + p = qc->client_new_token.data; + if (p == NULL) { + + p = njt_pnalloc(c->pool, NJT_QUIC_MAX_NEW_TOKEN); + if (p == NULL) { + return NJT_ERROR; + } + + qc->client_new_token.data = p; + } + + /* + * currently, keep only one token, and rewrite if multiple + * tokens received. + * + * the token is for use in 'future connections', so it is not + * really used currently. + */ + + njt_memcpy(p, f->data, f->length); + qc->client_new_token.len = f->length; + + njt_log_debug2(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic new token received: %*xs", f->length, f->data); + + return NJT_OK; +} static njt_int_t njt_quic_retire_client_id(njt_connection_t *c, njt_quic_client_id_t *cid) diff --git a/src/event/quic/njt_event_quic_connid.h b/src/event/quic/njt_event_quic_connid.h index 0bf55568..08464dac 100644 --- a/src/event/quic/njt_event_quic_connid.h +++ b/src/event/quic/njt_event_quic_connid.h @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -18,8 +19,12 @@ njt_int_t njt_quic_handle_retire_connection_id_frame(njt_connection_t *c, njt_int_t njt_quic_handle_new_connection_id_frame(njt_connection_t *c, njt_quic_new_conn_id_frame_t *f); +njt_int_t njt_quic_handle_new_token_frame(njt_connection_t *c, + njt_quic_new_token_frame_t *f); + njt_int_t njt_quic_create_sockets(njt_connection_t *c); -njt_int_t njt_quic_create_server_id(njt_connection_t *c, u_char *id); +njt_int_t njt_quic_create_server_id(njt_connection_t *c, u_char *id, + njt_uint_t client); njt_quic_client_id_t *njt_quic_create_client_id(njt_connection_t *c, njt_str_t *id, uint64_t seqnum, u_char *token); diff --git a/src/event/quic/njt_event_quic_migration.c b/src/event/quic/njt_event_quic_migration.c index db99be1f..00bcdc31 100644 --- a/src/event/quic/njt_event_quic_migration.c +++ b/src/event/quic/njt_event_quic_migration.c @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -181,13 +182,7 @@ valid: ctx = njt_quic_get_send_ctx(qc, ssl_encryption_application); qc->rst_pnum = ctx->pnum; - njt_memzero(&qc->congestion, sizeof(njt_quic_congestion_t)); - - qc->congestion.window = njt_min(10 * qc->tp.max_udp_payload_size, - njt_max(2 * qc->tp.max_udp_payload_size, - 14720)); - qc->congestion.ssthresh = (size_t) -1; - qc->congestion.recovery_start = njt_current_msec; + njt_quic_congestion_reset(qc); njt_quic_init_rtt(qc); } @@ -313,7 +308,7 @@ njt_quic_set_path(njt_connection_t *c, njt_quic_header_t *pkt) len = pkt->raw->last - pkt->raw->start; - if (c->udp->buffer == NULL) { + if (c->udp->buffer == NULL && !qc->client) { /* first ever packet in connection, path already exists */ path = qc->path; goto update; diff --git a/src/event/quic/njt_event_quic_openssl_compat.c b/src/event/quic/njt_event_quic_openssl_compat.c index 112fb2e8..702233f0 100644 --- a/src/event/quic/njt_event_quic_openssl_compat.c +++ b/src/event/quic/njt_event_quic_openssl_compat.c @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -208,6 +209,10 @@ njt_quic_compat_keylog_callback(const SSL *ssl, const char *line) com = qc->compat; cipher = SSL_get_current_cipher(ssl); + if (qc->client) { + write = !write; + } + if (write) { com->method->set_write_secret((SSL *) ssl, level, cipher, secret, n); com->write_level = level; diff --git a/src/event/quic/njt_event_quic_output.c b/src/event/quic/njt_event_quic_output.c index bb36fca2..fb9b0994 100644 --- a/src/event/quic/njt_event_quic_output.c +++ b/src/event/quic/njt_event_quic_output.c @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -400,6 +401,7 @@ njt_quic_send_segments(njt_connection_t *c, u_char *buf, size_t len, struct iovec iov; struct msghdr msg; struct cmsghdr *cmsg; + njt_quic_connection_t *qc; #if (NJT_HAVE_ADDRINFO_CMSG) char msg_control[CMSG_SPACE(sizeof(uint16_t)) @@ -417,8 +419,13 @@ njt_quic_send_segments(njt_connection_t *c, u_char *buf, size_t len, msg.msg_iov = &iov; msg.msg_iovlen = 1; - msg.msg_name = sockaddr; - msg.msg_namelen = socklen; + qc = njt_quic_get_connection(c); + + if (qc == NULL || !qc->client) { + /* TODO: *BSD: socket already connected */ + msg.msg_name = sockaddr; + msg.msg_namelen = socklen; + } msg.msg_control = msg_control; msg.msg_controllen = sizeof(msg_control); @@ -468,15 +475,36 @@ njt_quic_get_padding_level(njt_connection_t *c) /* * RFC 9000, 14.1. Initial Datagram Size - * - * Similarly, a server MUST expand the payload of all UDP datagrams - * carrying ack-eliciting Initial packets to at least the smallest - * allowed maximum datagram size of 1200 bytes. */ qc = njt_quic_get_connection(c); ctx = njt_quic_get_send_ctx(qc, ssl_encryption_initial); + if (qc->client) { + + /* + * A client MUST expand the payload of all UDP datagrams carrying + * Initial packets to at least the smallest allowed maximum datagram + * size of 1200 bytes + */ + + for (i = 0; i + 1 < NJT_QUIC_SEND_CTX_LAST; i++) { + ctx = &qc->send_ctx[i + 1]; + + if (njt_queue_empty(&ctx->frames)) { + break; + } + } + + return i; + } + + /* + * Similarly, a server MUST expand the payload of all UDP datagrams + * carrying ack-eliciting Initial packets to at least the smallest + * allowed maximum datagram size of 1200 bytes. + */ + for (q = njt_queue_head(&ctx->frames); q != njt_queue_sentinel(&ctx->frames); q = njt_queue_next(q)) @@ -661,6 +689,11 @@ njt_quic_init_packet(njt_connection_t *c, njt_quic_send_ctx_t *ctx, if (ctx->level == ssl_encryption_initial) { pkt->flags |= NJT_QUIC_PKT_LONG | NJT_QUIC_PKT_INITIAL; + if (qc->client_retry.len) { + pkt->token = qc->client_retry; + pkt->token.len = qc->client_retry.len; + } + } else if (ctx->level == ssl_encryption_handshake) { pkt->flags |= NJT_QUIC_PKT_LONG | NJT_QUIC_PKT_HANDSHAKE; @@ -696,6 +729,7 @@ njt_quic_send(njt_connection_t *c, u_char *buf, size_t len, struct cmsghdr *cmsg; char msg_control[CMSG_SPACE(sizeof(njt_addrinfo_t))]; #endif + njt_quic_connection_t *qc; njt_memzero(&msg, sizeof(struct msghdr)); @@ -705,8 +739,13 @@ njt_quic_send(njt_connection_t *c, u_char *buf, size_t len, msg.msg_iov = &iov; msg.msg_iovlen = 1; - msg.msg_name = sockaddr; - msg.msg_namelen = socklen; + qc = njt_quic_get_connection(c); + + if (qc == NULL || !qc->client) { + /* TODO: *BSD: socket already connected */ + msg.msg_name = sockaddr; + msg.msg_namelen = socklen; + } #if (NJT_HAVE_ADDRINFO_CMSG) if (c->listening && c->listening->wildcard && c->local_sockaddr) { @@ -921,6 +960,10 @@ njt_quic_send_early_cc(njt_connection_t *c, njt_quic_header_t *inpkt, njt_memzero(&keys, sizeof(njt_quic_keys_t)); + /* + * njt_quic_send_early_cc() is only called from token check, i.e. server + * thus keys.client = 0 + */ pkt.keys = &keys; if (njt_quic_keys_set_initial_secret(pkt.keys, &inpkt->dcid, c->log) @@ -1217,6 +1260,14 @@ njt_quic_frame_sendto(njt_connection_t *c, njt_quic_frame_t *frame, njt_quic_init_packet(c, ctx, &pkt, path); + min = njt_quic_path_limit(c, path, min); + if (qc->client && frame->level == ssl_encryption_initial + && min < NJT_QUIC_MIN_INITIAL_SIZE) + { + /* client must expand all initial packets */ + min = NJT_QUIC_MIN_INITIAL_SIZE; + } + min_payload = njt_quic_payload_size(&pkt, min); max_payload = njt_quic_payload_size(&pkt, max); @@ -1306,8 +1357,25 @@ size_t njt_quic_path_limit(njt_connection_t *c, njt_quic_path_t *path, size_t size) { off_t max; + njt_quic_connection_t *qc; if (!path->validated) { + + qc = njt_quic_get_connection(c); + + if (qc->client) { + + /* + * RFC 9000 21.1.1.1. Anti-Amplification + * + * The anti-amplification limit does not apply to clients when + * establishing a new connection or when initiating connection + * migration. + */ + + return size; + } + max = path->received * 3; max = (path->sent >= max) ? 0 : max - path->sent; diff --git a/src/event/quic/njt_event_quic_protection.c b/src/event/quic/njt_event_quic_protection.c index 6182b23b..2c020c07 100644 --- a/src/event/quic/njt_event_quic_protection.c +++ b/src/event/quic/njt_event_quic_protection.c @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -41,6 +42,8 @@ static njt_int_t njt_quic_crypto_hp_init(const EVP_CIPHER *cipher, static njt_int_t njt_quic_crypto_hp(njt_quic_secret_t *s, u_char *out, u_char *in, njt_log_t *log); static void njt_quic_crypto_hp_cleanup(njt_quic_secret_t *s); +static njt_quic_secret_t *njt_quic_select_secret(njt_quic_keys_t *keys, + enum ssl_encryption_level_t level, njt_uint_t is_write); static njt_int_t njt_quic_create_packet(njt_quic_header_t *pkt, njt_str_t *res); @@ -198,13 +201,13 @@ njt_quic_keys_set_initial_secret(njt_quic_keys_t *keys, njt_str_t *secret, return NJT_ERROR; } - if (njt_quic_crypto_init(ciphers.c, client, &client_key, 0, log) + if (njt_quic_crypto_init(ciphers.c, client, &client_key, keys->client, log) == NJT_ERROR) { return NJT_ERROR; } - if (njt_quic_crypto_init(ciphers.c, server, &server_key, 1, log) + if (njt_quic_crypto_init(ciphers.c, server, &server_key, !keys->client, log) == NJT_ERROR) { goto failed; @@ -656,6 +659,15 @@ njt_quic_crypto_hp_cleanup(njt_quic_secret_t *s) } } +static njt_quic_secret_t * +njt_quic_select_secret(njt_quic_keys_t *keys, + enum ssl_encryption_level_t level, njt_uint_t is_write) +{ + return keys->client ? (is_write ? &keys->secrets[level].client + : &keys->secrets[level].server) + : (is_write ? &keys->secrets[level].server + : &keys->secrets[level].client); +} njt_int_t njt_quic_keys_set_encryption_secret(njt_log_t *log, njt_uint_t is_write, @@ -670,8 +682,7 @@ njt_quic_keys_set_encryption_secret(njt_log_t *log, njt_uint_t is_write, njt_quic_secret_t *peer_secret; njt_quic_ciphers_t ciphers; - peer_secret = is_write ? &keys->secrets[level].server - : &keys->secrets[level].client; + peer_secret = njt_quic_select_secret(keys, level, is_write); keys->cipher = SSL_CIPHER_get_id(cipher); @@ -728,11 +739,11 @@ njt_uint_t njt_quic_keys_available(njt_quic_keys_t *keys, enum ssl_encryption_level_t level, njt_uint_t is_write) { - if (is_write == 0) { - return keys->secrets[level].client.ctx != NULL; - } + njt_quic_secret_t *s; + + s = njt_quic_select_secret(keys, level, is_write); - return keys->secrets[level].server.ctx != NULL; + return (s->ctx != NULL); } @@ -835,13 +846,15 @@ njt_quic_keys_update(njt_event_t *ev) } } - if (njt_quic_crypto_init(ciphers.c, &next->client, &client_key, 0, c->log) + if (njt_quic_crypto_init(ciphers.c, &next->client, &client_key, + qc->client, c->log) == NJT_ERROR) { goto failed; } - if (njt_quic_crypto_init(ciphers.c, &next->server, &server_key, 1, c->log) + if (njt_quic_crypto_init(ciphers.c, &next->server, &server_key, + !qc->client, c->log) == NJT_ERROR) { goto failed; @@ -855,6 +868,16 @@ njt_quic_keys_update(njt_event_t *ev) njt_explicit_memzero(client_key.data, client_key.len); njt_explicit_memzero(server_key.data, server_key.len); + if (qc->switch_keys) { + qc->key_phase ^= 1; + + njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic switching keys, phase: %ui", qc->key_phase); + + njt_quic_keys_switch(c, qc->keys); + qc->switch_keys = 0; + } + return; failed: @@ -905,7 +928,8 @@ njt_quic_create_packet(njt_quic_header_t *pkt, njt_str_t *res) "quic ad len:%uz %xV", ad.len, &ad); #endif - secret = &pkt->keys->secrets[pkt->level].server; + secret = pkt->keys->client ? &pkt->keys->secrets[pkt->level].client + : &pkt->keys->secrets[pkt->level].server; njt_memcpy(nonce, secret->iv.data, secret->iv.len); njt_quic_compute_nonce(nonce, sizeof(nonce), pkt->number); @@ -933,12 +957,9 @@ njt_quic_create_packet(njt_quic_header_t *pkt, njt_str_t *res) return NJT_OK; } - -static njt_int_t -njt_quic_create_retry_packet(njt_quic_header_t *pkt, njt_str_t *res) +njt_int_t +njt_quic_retry_seal(njt_str_t *ad, njt_str_t *itag, njt_log_t *log) { - u_char *start; - njt_str_t ad, itag; njt_quic_md_t key; njt_quic_secret_t secret; njt_quic_ciphers_t ciphers; @@ -950,15 +971,10 @@ njt_quic_create_retry_packet(njt_quic_header_t *pkt, njt_str_t *res) "\x46\x15\x99\xd3\x5d\x63\x2b\xf2\x23\x98\x25\xbb"; static njt_str_t in = njt_string(""); - ad.data = res->data; - ad.len = njt_quic_create_retry_itag(pkt, ad.data, &start); - itag.data = ad.data + ad.len; - itag.len = NJT_QUIC_TAG_LEN; - -#ifdef NJT_QUIC_DEBUG_CRYPTO +#ifdef njt_QUIC_DEBUG_CRYPTO njt_log_debug2(NJT_LOG_DEBUG_EVENT, pkt->log, 0, - "quic retry itag len:%uz %xV", ad.len, &ad); + "quic retry itag len:%uz %xV", ad->len, ad); #endif if (njt_quic_ciphers(NJT_QUIC_INITIAL_CIPHER, &ciphers) == NJT_ERROR) { @@ -969,13 +985,13 @@ njt_quic_create_retry_packet(njt_quic_header_t *pkt, njt_str_t *res) njt_memcpy(key.data, key_data, sizeof(key_data)); secret.iv.len = NJT_QUIC_IV_LEN; - if (njt_quic_crypto_init(ciphers.c, &secret, &key, 1, pkt->log) + if (njt_quic_crypto_init(ciphers.c, &secret, &key, 1, log) == NJT_ERROR) { return NJT_ERROR; } - if (njt_quic_crypto_seal(&secret, &itag, nonce, &in, &ad, pkt->log) + if (njt_quic_crypto_seal(&secret, itag, nonce, &in, ad, log) != NJT_OK) { njt_quic_crypto_cleanup(&secret); @@ -984,6 +1000,25 @@ njt_quic_create_retry_packet(njt_quic_header_t *pkt, njt_str_t *res) njt_quic_crypto_cleanup(&secret); + return NJT_OK; +} + +static njt_int_t +njt_quic_create_retry_packet(njt_quic_header_t *pkt, njt_str_t *res) +{ + u_char *start; + njt_str_t ad, itag; + + ad.data = res->data; + ad.len = njt_quic_create_retry_itag(pkt, ad.data, &start); + + itag.data = ad.data + ad.len; + itag.len = NJT_QUIC_TAG_LEN; + + if (njt_quic_retry_seal(&ad, &itag, pkt->log) != NJT_OK) { + return NJT_ERROR; + } + res->len = itag.data + itag.len - start; res->data = start; @@ -1117,10 +1152,14 @@ njt_quic_decrypt(njt_quic_header_t *pkt, uint64_t *largest_pn) njt_int_t pnl; njt_str_t in, ad; njt_uint_t key_phase; - njt_quic_secret_t *secret; + njt_quic_secret_t *secret, *next; uint8_t nonce[NJT_QUIC_IV_LEN], mask[NJT_QUIC_HP_LEN]; - secret = &pkt->keys->secrets[pkt->level].client; + secret = pkt->keys->client ? &pkt->keys->secrets[pkt->level].server + : &pkt->keys->secrets[pkt->level].client; + + next = pkt->keys->client ? &pkt->keys->next_key.server + : &pkt->keys->next_key.client; p = pkt->raw->pos; len = pkt->data + pkt->len - p; @@ -1151,8 +1190,12 @@ njt_quic_decrypt(njt_quic_header_t *pkt, uint64_t *largest_pn) if (njt_quic_short_pkt(pkt->flags)) { key_phase = (pkt->flags & NJT_QUIC_PKT_KPHASE) != 0; - if (key_phase != pkt->key_phase) { - secret = &pkt->keys->next_key.client; + /* + * we don't want to switch to next key if we don't have it yet; + * the fact of being here may be caused by the header corruption + */ + if (key_phase != pkt->key_phase && next->ctx != NULL) { + secret = next; pkt->key_update = 1; } } diff --git a/src/event/quic/njt_event_quic_protection.h b/src/event/quic/njt_event_quic_protection.h index 7fe1fd5a..752fedd9 100644 --- a/src/event/quic/njt_event_quic_protection.h +++ b/src/event/quic/njt_event_quic_protection.h @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -65,6 +66,7 @@ struct njt_quic_keys_s { njt_quic_secrets_t secrets[NJT_QUIC_ENCRYPTION_LAST]; njt_quic_secrets_t next_key; njt_uint_t cipher; + njt_uint_t client; }; @@ -116,6 +118,6 @@ njt_int_t njt_quic_crypto_seal(njt_quic_secret_t *s, njt_str_t *out, void njt_quic_crypto_cleanup(njt_quic_secret_t *s); njt_int_t njt_quic_hkdf_expand(njt_quic_hkdf_t *hkdf, const EVP_MD *digest, njt_log_t *log); - +njt_int_t njt_quic_retry_seal(njt_str_t *ad, njt_str_t *itag, njt_log_t *log); #endif /* _NJT_EVENT_QUIC_PROTECTION_H_INCLUDED_ */ diff --git a/src/event/quic/njt_event_quic_socket.c b/src/event/quic/njt_event_quic_socket.c index adcbfa72..4ad78a17 100644 --- a/src/event/quic/njt_event_quic_socket.c +++ b/src/event/quic/njt_event_quic_socket.c @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -36,10 +37,15 @@ njt_quic_open_sockets(njt_connection_t *c, njt_quic_connection_t *qc, njt_queue_init(&qc->client_ids); njt_queue_init(&qc->free_client_ids); - qc->tp.original_dcid.len = pkt->odcid.len; - qc->tp.original_dcid.data = njt_pstrdup(c->pool, &pkt->odcid); - if (qc->tp.original_dcid.data == NULL) { - return NJT_ERROR; + if (qc->client) { + qc->tp.original_dcid.len = 0; + + } else { + qc->tp.original_dcid.len = pkt->odcid.len; + qc->tp.original_dcid.data = njt_pstrdup(c->pool, &pkt->odcid); + if (qc->tp.original_dcid.data == NULL) { + return NJT_ERROR; + } } /* socket to use for further processing (id auto-generated) */ @@ -67,6 +73,10 @@ njt_quic_open_sockets(njt_connection_t *c, njt_quic_connection_t *qc, /* njt_quic_get_connection(c) macro is now usable */ + if (qc->client) { + return NJT_OK; + } + /* we have a client identified by scid */ cid = njt_quic_create_client_id(c, &pkt->scid, 0, NULL); if (cid == NULL) { @@ -105,7 +115,9 @@ njt_quic_open_sockets(njt_connection_t *c, njt_quic_connection_t *qc, failed: - njt_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + if (!qc->client) { + njt_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + } c->udp = NULL; return NJT_ERROR; @@ -136,7 +148,7 @@ njt_quic_create_socket(njt_connection_t *c, njt_quic_connection_t *qc) } sock->sid.len = NJT_QUIC_SERVER_CID_LEN; - if (njt_quic_create_server_id(c, sock->sid.id) != NJT_OK) { + if (njt_quic_create_server_id(c, sock->sid.id, qc->client) != NJT_OK) { return NULL; } @@ -156,7 +168,10 @@ njt_quic_close_socket(njt_connection_t *c, njt_quic_socket_t *qsock) njt_queue_remove(&qsock->queue); njt_queue_insert_head(&qc->free_sockets, &qsock->queue); - njt_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + if (!qc->client) { + njt_rbtree_delete(&c->listening->rbtree, &qsock->udp.node); + } + qc->nsockets--; njt_log_debug2(NJT_LOG_DEBUG_EVENT, c->log, 0, @@ -177,15 +192,16 @@ njt_quic_listen(njt_connection_t *c, njt_quic_connection_t *qc, id.data = sid->id; id.len = sid->len; - qsock->udp.connection = c; - qsock->udp.node.key = njt_crc32_long(id.data, id.len); - qsock->udp.key = id; - //udp traffic hack, init real_sock to -1 qsock->udp.real_sock = (njt_socket_t)-1; - njt_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); + if (!qc->client) { + qsock->udp.connection = c; + qsock->udp.node.key = njt_crc32_long(id.data, id.len); + qsock->udp.key = id; + njt_rbtree_insert(&c->listening->rbtree, &qsock->udp.node); + } njt_queue_insert_tail(&qc->sockets, &qsock->queue); qc->nsockets++; @@ -239,3 +255,33 @@ njt_quic_find_socket(njt_connection_t *c, uint64_t seqnum) return NULL; } + +njt_quic_socket_t * +njt_quic_find_socket_by_id(njt_connection_t *c, njt_str_t *key) +{ + njt_queue_t *q; + njt_quic_socket_t *qsock; + njt_quic_connection_t *qc; + + if (key->len == 0) { + return NULL; + } + + qc = njt_quic_get_connection(c); + + for (q = njt_queue_head(&qc->sockets); + q != njt_queue_sentinel(&qc->sockets); + q = njt_queue_next(q)) + { + qsock = njt_queue_data(q, njt_quic_socket_t, queue); + + if (njt_memn2cmp(key->data, qsock->sid.id, + key->len, qsock->sid.len) + == 0) + { + return qsock; + } + } + + return NULL; +} \ No newline at end of file diff --git a/src/event/quic/njt_event_quic_socket.h b/src/event/quic/njt_event_quic_socket.h index fb4123c6..06c95d56 100644 --- a/src/event/quic/njt_event_quic_socket.h +++ b/src/event/quic/njt_event_quic_socket.h @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -24,6 +25,8 @@ njt_int_t njt_quic_listen(njt_connection_t *c, njt_quic_connection_t *qc, void njt_quic_close_socket(njt_connection_t *c, njt_quic_socket_t *qsock); njt_quic_socket_t *njt_quic_find_socket(njt_connection_t *c, uint64_t seqnum); +njt_quic_socket_t *njt_quic_find_socket_by_id(njt_connection_t *c, + njt_str_t *key); #endif /* _NJT_EVENT_QUIC_SOCKET_H_INCLUDED_ */ diff --git a/src/event/quic/njt_event_quic_ssl.c b/src/event/quic/njt_event_quic_ssl.c index 48e2c30e..a1ee3a67 100644 --- a/src/event/quic/njt_event_quic_ssl.c +++ b/src/event/quic/njt_event_quic_ssl.c @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -196,18 +197,18 @@ njt_quic_add_handshake_data(njt_ssl_conn_t *ssl_conn, */ #if defined(TLSEXT_TYPE_application_layer_protocol_negotiation) + if (!qc->client) { + SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); - SSL_get0_alpn_selected(ssl_conn, &alpn_data, &alpn_len); + if (alpn_len == 0) { + qc->error = NJT_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL); + qc->error_reason = "unsupported protocol in ALPN extension"; - if (alpn_len == 0) { - qc->error = NJT_QUIC_ERR_CRYPTO(SSL_AD_NO_APPLICATION_PROTOCOL); - qc->error_reason = "unsupported protocol in ALPN extension"; - - njt_log_error(NJT_LOG_INFO, c->log, 0, - "quic unsupported protocol in ALPN extension"); - return 0; + njt_log_error(NJT_LOG_INFO, c->log, 0, + "quic unsupported protocol in ALPN extension"); + return 0; + } } - #endif SSL_get_peer_quic_transport_params(ssl_conn, &client_params, @@ -216,39 +217,48 @@ njt_quic_add_handshake_data(njt_ssl_conn_t *ssl_conn, njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, "quic SSL_get_peer_quic_transport_params():" " params_len:%ui", client_params_len); + if (client_params_len) { - if (client_params_len == 0) { - /* RFC 9001, 8.2. QUIC Transport Parameters Extension */ - qc->error = NJT_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION); - qc->error_reason = "missing transport parameters"; + p = (u_char *) client_params; + end = p + client_params_len; - njt_log_error(NJT_LOG_INFO, c->log, 0, - "missing transport parameters"); - return 0; - } + /* defaults for parameters not sent by client */ + njt_memcpy(&ctp, &qc->ctp, sizeof(njt_quic_tp_t)); - p = (u_char *) client_params; - end = p + client_params_len; + if (njt_quic_parse_transport_params(p, end, &ctp, c->log, + qc->client) + != NJT_OK) + { + qc->error = NJT_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; + qc->error_reason = "failed to process transport parameters"; - /* defaults for parameters not sent by client */ - njt_memcpy(&ctp, &qc->ctp, sizeof(njt_quic_tp_t)); + return 0; + } - if (njt_quic_parse_transport_params(p, end, &ctp, c->log) - != NJT_OK) - { - qc->error = NJT_QUIC_ERR_TRANSPORT_PARAMETER_ERROR; - qc->error_reason = "failed to process transport parameters"; + if (njt_quic_apply_transport_params(c, &ctp) != NJT_OK) { + return 0; + } - return 0; - } + qc->client_tp_done = 1; + + } else { - if (njt_quic_apply_transport_params(c, &ctp) != NJT_OK) { + if (qc->client) { + /* when we send 1st packet, no response from server yet */ + goto skip; + } + + /* RFC 9001, 8.2. QUIC Transport Parameters Extension */ + qc->error = NJT_QUIC_ERR_CRYPTO(SSL_AD_MISSING_EXTENSION); + qc->error_reason = "missing transport parameters"; + + njt_log_error(NJT_LOG_INFO, c->log, 0, + "missing transport parameters"); return 0; } - - qc->client_tp_done = 1; } +skip: ctx = njt_quic_get_send_ctx(qc, level); out = njt_quic_copy_buffer(c, (u_char *) data, len); @@ -451,21 +461,50 @@ njt_quic_crypto_input(njt_connection_t *c, njt_chain_t *data, njt_ssl_handshake_log(c); #endif - c->ssl->handshaked = 1; - - frame = njt_quic_alloc_frame(c); - if (frame == NULL) { +#if !defined(NJT_QUIC_OPENSSL_COMPAT) + /* missing in compat, session reuse is not going to work there */ + if (SSL_process_quic_post_handshake(c->ssl->connection) != 1) { return NJT_ERROR; } +#endif - frame->level = ssl_encryption_application; - frame->type = NJT_QUIC_FT_HANDSHAKE_DONE; - njt_quic_queue_frame(qc, frame); + c->ssl->handshaked = 1; + + if (qc->client) { + if (level != ssl_encryption_application) { + /* + * Server sends TLS Handshake Finished twice: + * in handshake and application level packets; + * + * perform h/s complete actions only once, when app received + */ + + return NJT_OK; + } + + /* flush output: need to ack with previous keys */ + if (njt_quic_output(c) != NJT_OK) { + return NJT_ERROR; + } + + /* initiate key update procedure */ + qc->switch_keys = 1; - if (qc->conf->retry) { - if (njt_quic_send_new_token(c, qc->path) != NJT_OK) { + } else { + frame = njt_quic_alloc_frame(c); + if (frame == NULL) { return NJT_ERROR; } + + frame->level = ssl_encryption_application; + frame->type = NJT_QUIC_FT_HANDSHAKE_DONE; + njt_quic_queue_frame(qc, frame); + + if (qc->conf->retry) { + if (njt_quic_send_new_token(c, qc->path) != NJT_OK) { + return NJT_ERROR; + } + } } /* @@ -486,7 +525,7 @@ njt_quic_crypto_input(njt_connection_t *c, njt_chain_t *data, njt_quic_discover_path_mtu(c, qc->path); - /* start accepting clients on negotiated number of server ids */ + /* start accepting packets on negotiated number of server ids */ if (njt_quic_create_sockets(c) != NJT_OK) { return NJT_ERROR; } @@ -506,6 +545,7 @@ njt_quic_init_connection(njt_connection_t *c) size_t clen; ssize_t len; njt_str_t dcid; + njt_uint_t flags; njt_ssl_conn_t *ssl_conn; njt_quic_socket_t *qsock; njt_quic_connection_t *qc; @@ -513,7 +553,14 @@ njt_quic_init_connection(njt_connection_t *c) qc = njt_quic_get_connection(c); - if (njt_ssl_create_connection(qc->conf->ssl, c, 0) != NJT_OK) { + flags = qc->client ? NJT_SSL_CLIENT : 0; + + if (c->ssl) { + /* retry packet case: reinit ssl */ + SSL_free(c->ssl->connection); + } + + if (njt_ssl_create_connection(qc->conf->ssl, c, flags) != NJT_OK) { return NJT_ERROR; } @@ -539,6 +586,10 @@ njt_quic_init_connection(njt_connection_t *c) return NJT_ERROR; } + if (qc->init_ssl && qc->init_ssl(c, qc->init_ssl_data) != NJT_OK) { + return NJT_ERROR; + } + #ifdef OPENSSL_INFO_QUIC if (SSL_CTX_get_max_early_data(qc->conf->ssl->ctx)) { SSL_set_quic_early_data_enabled(ssl_conn, 1); @@ -556,7 +607,8 @@ njt_quic_init_connection(njt_connection_t *c) return NJT_ERROR; } - len = njt_quic_create_transport_params(NULL, NULL, &qc->tp, &clen); + len = njt_quic_create_transport_params(NULL, NULL, &qc->tp, &clen, + qc->client); /* always succeeds */ p = njt_pnalloc(c->pool, len); @@ -564,7 +616,8 @@ njt_quic_init_connection(njt_connection_t *c) return NJT_ERROR; } - len = njt_quic_create_transport_params(p, p + len, &qc->tp, NULL); + len = njt_quic_create_transport_params(p, p + len, &qc->tp, NULL, + qc->client); if (len < 0) { return NJT_ERROR; } @@ -590,3 +643,33 @@ njt_quic_init_connection(njt_connection_t *c) return NJT_OK; } + + +njt_int_t +njt_quic_client_handshake(njt_connection_t *c) +{ + int n, sslerr; + njt_ssl_conn_t *ssl_conn; + + if (njt_quic_init_connection(c) != NJT_OK) { + return NJT_ERROR; + } + + ssl_conn = c->ssl->connection; + + n = SSL_do_handshake(ssl_conn); + + if (n <= 0) { + sslerr = SSL_get_error(ssl_conn, n); + + njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", + sslerr); + + if (sslerr != SSL_ERROR_WANT_READ) { + njt_ssl_error(NJT_LOG_ERR, c->log, 0, "SSL_do_handshake() failed"); + return NJT_ERROR; + } + } + + return NJT_OK; +} \ No newline at end of file diff --git a/src/event/quic/njt_event_quic_ssl.h b/src/event/quic/njt_event_quic_ssl.h index 11c9fb53..04d7476f 100644 --- a/src/event/quic/njt_event_quic_ssl.h +++ b/src/event/quic/njt_event_quic_ssl.h @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -17,4 +18,6 @@ njt_int_t njt_quic_init_connection(njt_connection_t *c); njt_int_t njt_quic_handle_crypto_frame(njt_connection_t *c, njt_quic_header_t *pkt, njt_quic_frame_t *frame); +njt_int_t njt_quic_client_handshake(njt_connection_t *c); + #endif /* _NJT_EVENT_QUIC_SSL_H_INCLUDED_ */ diff --git a/src/event/quic/njt_event_quic_streams.c b/src/event/quic/njt_event_quic_streams.c index a0bed309..3b1cf056 100644 --- a/src/event/quic/njt_event_quic_streams.c +++ b/src/event/quic/njt_event_quic_streams.c @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -13,6 +14,17 @@ #define NJT_QUIC_STREAM_GONE (void *) -1 +#define njt_quic_uni_stream(id) \ + ((id) & NJT_QUIC_STREAM_UNIDIRECTIONAL) + +#define njt_quic_out_stream(qc, id) \ + (((qc)->client && (((id) & NJT_QUIC_STREAM_SERVER_INITIATED) == 0)) \ + || ((!(qc)->client) && ((id) & NJT_QUIC_STREAM_SERVER_INITIATED))) + +#define njt_quic_input_stream(qc, id) \ + (((qc)->client && ((id) & NJT_QUIC_STREAM_SERVER_INITIATED)) \ + || ((!(qc)->client) && (((id) & NJT_QUIC_STREAM_SERVER_INITIATED) == 0))) + static njt_int_t njt_quic_do_reset_stream(njt_quic_stream_t *qs, njt_uint_t err); @@ -25,6 +37,8 @@ static void njt_quic_init_streams_handler(njt_connection_t *c); static njt_int_t njt_quic_do_init_streams(njt_connection_t *c); static njt_quic_stream_t *njt_quic_create_stream(njt_connection_t *c, uint64_t id); +static void njt_quic_stream_init_state(njt_quic_connection_t *qc, + njt_quic_stream_t *qs); static void njt_quic_empty_handler(njt_event_t *ev); static ssize_t njt_quic_stream_recv(njt_connection_t *c, u_char *buf, size_t size); @@ -32,6 +46,10 @@ static ssize_t njt_quic_stream_send(njt_connection_t *c, u_char *buf, size_t size); static njt_chain_t *njt_quic_stream_send_chain(njt_connection_t *c, njt_chain_t *in, off_t limit); +static ssize_t njt_quic_stream_recv_chain(njt_connection_t *c, + njt_chain_t *in, off_t limit); +static void njt_quic_copy_chain_data(njt_chain_t *dst_chain, + njt_chain_t *src_chain); static njt_int_t njt_quic_stream_flush(njt_quic_stream_t *qs); static void njt_quic_stream_cleanup_handler(void *data); static njt_int_t njt_quic_close_stream(njt_quic_stream_t *qs); @@ -50,57 +68,46 @@ njt_quic_open_stream(njt_connection_t *c, njt_uint_t bidi) njt_connection_t *pc, *sc; njt_quic_stream_t *qs; njt_quic_connection_t *qc; + njt_quic_stream_ctl_t *sctl; + njt_quic_stream_peer_t *peer; pc = c->quic ? c->quic->parent : c; qc = njt_quic_get_connection(pc); if (qc->closing) { + njt_log_debug0(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic open stream failed: already closing"); return NULL; } - if (bidi) { - if (qc->streams.server_streams_bidi - >= qc->streams.server_max_streams_bidi) - { - njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, - "quic too many server bidi streams:%uL", - qc->streams.server_streams_bidi); + peer = qc->client ? &qc->streams.client : &qc->streams.server; + sctl = bidi ? &peer->bidi : &peer->uni; + + if (sctl->count >= sctl->max) { + njt_log_debug3(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic too many %s %s streams:%uL", + qc->client ? "client" : "server", bidi ? "bidi": "uni", + sctl->count); return NULL; } - id = (qc->streams.server_streams_bidi << 2) - | NJT_QUIC_STREAM_SERVER_INITIATED; + id = (sctl->count << 2); - njt_log_debug3(NJT_LOG_DEBUG_EVENT, c->log, 0, - "quic creating server bidi stream" - " streams:%uL max:%uL id:0x%xL", - qc->streams.server_streams_bidi, - qc->streams.server_max_streams_bidi, id); - - qc->streams.server_streams_bidi++; + if (!bidi) { + id |= NJT_QUIC_STREAM_UNIDIRECTIONAL; + } - } else { - if (qc->streams.server_streams_uni - >= qc->streams.server_max_streams_uni) - { - njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, - "quic too many server uni streams:%uL", - qc->streams.server_streams_uni); - return NULL; + if (!qc->client) { + id |= NJT_QUIC_STREAM_SERVER_INITIATED; } - id = (qc->streams.server_streams_uni << 2) - | NJT_QUIC_STREAM_SERVER_INITIATED - | NJT_QUIC_STREAM_UNIDIRECTIONAL; - - njt_log_debug3(NJT_LOG_DEBUG_EVENT, c->log, 0, - "quic creating server uni stream" + njt_log_debug5(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic creating %s %s stream" " streams:%uL max:%uL id:0x%xL", - qc->streams.server_streams_uni, - qc->streams.server_max_streams_uni, id); + qc->client ? "client" : "server", bidi ? "bidi" : "uni", + sctl->count, sctl->max, id); - qc->streams.server_streams_uni++; - } + sctl->count++; qs = njt_quic_create_stream(pc, id); if (qs == NULL) { @@ -237,6 +244,11 @@ njt_quic_close_streams(njt_connection_t *c, njt_quic_connection_t *qc) njt_int_t njt_quic_reset_stream(njt_connection_t *c, njt_uint_t err) { + if (!c->quic->parent->ssl->handshaked) { + /* the stream was created early, do not try to send anything */ + return NJT_OK; + } + return njt_quic_do_reset_stream(c->quic, err); } @@ -290,6 +302,15 @@ njt_quic_do_reset_stream(njt_quic_stream_t *qs, njt_uint_t err) njt_int_t njt_quic_shutdown_stream(njt_connection_t *c, int how) { + njt_connection_t *pc; + + pc = c->quic->parent; + + if (!pc->ssl->handshaked) { + /* the stream was created early, do not try to send anything */ + return NJT_OK; + } + if (how == NJT_RDWR_SHUTDOWN || how == NJT_WRITE_SHUTDOWN) { if (njt_quic_shutdown_stream_send(c) != NJT_OK) { return NJT_ERROR; @@ -378,6 +399,8 @@ njt_quic_get_stream(njt_connection_t *c, uint64_t id) njt_event_t *rev; njt_quic_stream_t *qs; njt_quic_connection_t *qc; + njt_quic_stream_ctl_t *sctl; + njt_quic_stream_peer_t *peer; qc = njt_quic_get_connection(c); @@ -394,10 +417,14 @@ njt_quic_get_stream(njt_connection_t *c, uint64_t id) njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, "quic stream id:0x%xL is missing", id); - if (id & NJT_QUIC_STREAM_UNIDIRECTIONAL) { + if (njt_quic_out_stream(qc, id)) { + /* stream is initiated by us, but peer is trying to use it */ + + peer = qc->client ? &qc->streams.client : &qc->streams.server; + + sctl = njt_quic_uni_stream(id) ? &peer->uni : &peer->bidi; - if (id & NJT_QUIC_STREAM_SERVER_INITIATED) { - if ((id >> 2) < qc->streams.server_streams_uni) { + if ((id >> 2) < sctl->count) { return NJT_QUIC_STREAM_GONE; } @@ -405,43 +432,30 @@ njt_quic_get_stream(njt_connection_t *c, uint64_t id) return NULL; } - if ((id >> 2) < qc->streams.client_streams_uni) { - return NJT_QUIC_STREAM_GONE; - } - - if ((id >> 2) >= qc->streams.client_max_streams_uni) { - qc->error = NJT_QUIC_ERR_STREAM_LIMIT_ERROR; - return NULL; - } - - min_id = (qc->streams.client_streams_uni << 2) - | NJT_QUIC_STREAM_UNIDIRECTIONAL; - qc->streams.client_streams_uni = (id >> 2) + 1; - - } else { + peer = qc->client ? &qc->streams.server: &qc->streams.client; + sctl = njt_quic_uni_stream(id) ? &peer->uni : &peer->bidi; - if (id & NJT_QUIC_STREAM_SERVER_INITIATED) { - if ((id >> 2) < qc->streams.server_streams_bidi) { - return NJT_QUIC_STREAM_GONE; - } + if ((id >> 2) < sctl->count) { + return NJT_QUIC_STREAM_GONE; + } - qc->error = NJT_QUIC_ERR_STREAM_STATE_ERROR; - return NULL; - } + if ((id >> 2) >= sctl->max) { + qc->error = NJT_QUIC_ERR_STREAM_LIMIT_ERROR; + return NULL; + } - if ((id >> 2) < qc->streams.client_streams_bidi) { - return NJT_QUIC_STREAM_GONE; - } + min_id = sctl->count << 2; - if ((id >> 2) >= qc->streams.client_max_streams_bidi) { - qc->error = NJT_QUIC_ERR_STREAM_LIMIT_ERROR; - return NULL; - } + if (qc->client) { + min_id++; + } - min_id = (qc->streams.client_streams_bidi << 2); - qc->streams.client_streams_bidi = (id >> 2) + 1; + if (njt_quic_uni_stream(id)) { + min_id |= NJT_QUIC_STREAM_UNIDIRECTIONAL; } + sctl->count = (id >> 2) + 1; + /* * RFC 9000, 2.1. Stream Types and Identifiers * @@ -504,8 +518,7 @@ njt_quic_reject_stream(njt_connection_t *c, uint64_t id) qc = njt_quic_get_connection(c); - code = (id & NJT_QUIC_STREAM_UNIDIRECTIONAL) - ? qc->conf->stream_reject_code_uni + code = njt_quic_uni_stream(id) ? qc->conf->stream_reject_code_uni : qc->conf->stream_reject_code_bidi; if (code == 0) { @@ -555,7 +568,7 @@ njt_quic_init_stream_handler(njt_event_t *ev) njt_log_debug0(NJT_LOG_DEBUG_EVENT, c->log, 0, "quic init stream"); - if ((qs->id & NJT_QUIC_STREAM_UNIDIRECTIONAL) == 0) { + if (!njt_quic_uni_stream(qs->id)) { c->write->active = 1; c->write->ready = 1; } @@ -756,17 +769,46 @@ njt_quic_create_stream(njt_connection_t *c, uint64_t id) sc->recv = njt_quic_stream_recv; sc->send = njt_quic_stream_send; sc->send_chain = njt_quic_stream_send_chain; + sc->recv_chain = njt_quic_stream_recv_chain; - sc->read->log = log; - sc->write->log = log; + sc->read->log = c->log; + sc->write->log = c->log; sc->read->handler = njt_quic_empty_handler; sc->write->handler = njt_quic_empty_handler; log->connection = sc->number; - if (id & NJT_QUIC_STREAM_UNIDIRECTIONAL) { - if (id & NJT_QUIC_STREAM_SERVER_INITIATED) { + njt_quic_stream_init_state(qc, qs); + + qs->recv_window = qs->recv_max_data; + + cln = njt_pool_cleanup_add(pool, 0); + if (cln == NULL) { + njt_close_connection(sc); + njt_destroy_pool(pool); + njt_queue_insert_tail(&qc->streams.free, &qs->queue); + njt_reusable_connection(c, reusable); + return NULL; + } + + cln->handler = njt_quic_stream_cleanup_handler; + cln->data = sc; + + njt_rbtree_insert(&qc->streams.tree, &qs->node); + + return qs; +} + +static void +njt_quic_stream_init_state(njt_quic_connection_t *qc, njt_quic_stream_t *qs) +{ + njt_uint_t out; + + out = njt_quic_out_stream(qc, qs->id); + + if (njt_quic_uni_stream(qs->id)) { + if (out) { qs->send_max_data = qc->ctp.initial_max_stream_data_uni; qs->recv_state = NJT_QUIC_STREAM_RECV_DATA_READ; qs->send_state = NJT_QUIC_STREAM_SEND_READY; @@ -778,7 +820,7 @@ njt_quic_create_stream(njt_connection_t *c, uint64_t id) } } else { - if (id & NJT_QUIC_STREAM_SERVER_INITIATED) { + if (out) { qs->send_max_data = qc->ctp.initial_max_stream_data_bidi_remote; qs->recv_max_data = qc->tp.initial_max_stream_data_bidi_local; @@ -790,27 +832,68 @@ njt_quic_create_stream(njt_connection_t *c, uint64_t id) qs->recv_state = NJT_QUIC_STREAM_RECV_RECV; qs->send_state = NJT_QUIC_STREAM_SEND_READY; } +} - qs->recv_window = qs->recv_max_data; - cln = njt_pool_cleanup_add(pool, 0); - if (cln == NULL) { - njt_close_connection(sc); - njt_destroy_pool(pool); - njt_queue_insert_tail(&qc->streams.free, &qs->queue); - njt_reusable_connection(c, reusable); - return NULL; +void +njt_quic_streams_init_state(njt_connection_t *c) +{ + njt_rbtree_t *tree; + njt_rbtree_node_t *node; + njt_quic_stream_t *qs; + njt_quic_connection_t *qc; + + qc = njt_quic_get_connection(c); + + tree = &qc->streams.tree; + + if (tree->root == tree->sentinel) { + return; } - cln->handler = njt_quic_stream_cleanup_handler; - cln->data = sc; + node = njt_rbtree_min(tree->root, tree->sentinel); - njt_rbtree_insert(&qc->streams.tree, &qs->node); + while (node) { + qs = (njt_quic_stream_t *) node; + node = njt_rbtree_next(tree, node); - return qs; + njt_quic_stream_init_state(qc, qs); + } } +void +njt_quic_streams_notify_write(njt_connection_t *c) +{ + njt_rbtree_t *tree; + njt_connection_t *sc; + njt_rbtree_node_t *node; + njt_quic_stream_t *qs; + njt_quic_connection_t *qc; + + qc = njt_quic_get_connection(c); + + tree = &qc->streams.tree; + + if (tree->root == tree->sentinel) { + return; + } + + node = njt_rbtree_min(tree->root, tree->sentinel); + + while (node) { + qs = (njt_quic_stream_t *) node; + node = njt_rbtree_next(tree, node); + + sc = qs->connection; + if (sc == NULL) { + continue; + } + + njt_post_event(sc->write, &njt_posted_events); + } +} + void njt_quic_cancelable_stream(njt_connection_t *c) { @@ -860,6 +943,8 @@ njt_quic_stream_recv(njt_connection_t *c, u_char *buf, size_t size) || qs->recv_state == NJT_QUIC_STREAM_RECV_RESET_READ) { qs->recv_state = NJT_QUIC_STREAM_RECV_RESET_READ; + njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL bad recv state", qs->id); return NJT_ERROR; } @@ -1003,6 +1088,129 @@ njt_quic_stream_send_chain(njt_connection_t *c, njt_chain_t *in, off_t limit) } +static ssize_t +njt_quic_stream_recv_chain(njt_connection_t *c, njt_chain_t *in, off_t limit) +{ + size_t len; + njt_buf_t *b; + njt_chain_t *cl, *out; + njt_event_t *rev; + njt_connection_t *pc; + njt_quic_stream_t *qs; + + qs = c->quic; + pc = qs->parent; + rev = c->read; + + if (qs->recv_state == NJT_QUIC_STREAM_RECV_RESET_RECVD + || qs->recv_state == NJT_QUIC_STREAM_RECV_RESET_READ) + { + qs->recv_state = NJT_QUIC_STREAM_RECV_RESET_READ; + njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL bad recv state", qs->id); + return NJT_ERROR; + } + + njt_log_debug1(NJT_LOG_DEBUG_EVENT, pc->log, 0, + "quic stream id:0x%xL recv chain", qs->id); + + len = 0; + for (cl = in; cl; cl = cl->next) { + len += cl->buf->end - cl->buf->last; + } + + if (limit && len > (size_t) limit) { + len = limit; + } + + out = njt_quic_read_buffer(pc, &qs->recv, len); + if (out == NJT_CHAIN_ERROR) { + return NJT_ERROR; + } + + len = 0; + + for (cl = out; cl; cl = cl->next) { + b = cl->buf; + len += b->last - b->pos; + } + + if (len == 0) { + rev->ready = 0; + + if (qs->recv_state == NJT_QUIC_STREAM_RECV_DATA_RECVD + && qs->recv_offset == qs->recv_final_size) + { + qs->recv_state = NJT_QUIC_STREAM_RECV_DATA_READ; + } + + if (qs->recv_state == NJT_QUIC_STREAM_RECV_DATA_READ) { + rev->eof = 1; + return 0; + } + + njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv chain() not ready", qs->id); + return NJT_AGAIN; + } + + njt_quic_copy_chain_data(in, out); + + njt_quic_free_chain(pc, out); + + njt_log_debug2(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic stream id:0x%xL recv chain len:%z", qs->id, len); + + if (njt_quic_update_flow(qs, qs->recv_offset + len) != NJT_OK) { + return NJT_ERROR; + } + + return len; +} + + +static void +njt_quic_copy_chain_data(njt_chain_t *dst_chain, njt_chain_t *src_chain) +{ + u_char *rpos, *wpos; + size_t data_size, buf_size, len; + njt_chain_t *src, *dst; + + src = src_chain; + dst = dst_chain; + + rpos = src->buf->pos; + wpos = dst->buf->last; + + while (src && dst) { + + data_size = src->buf->last - rpos; + buf_size = dst->buf->end - wpos; + + len = njt_min(data_size, buf_size); + + njt_memcpy(wpos, rpos, len); + + rpos += len; + wpos += len; + + if (rpos == src->buf->last) { + src = src->next; + if (src) { + rpos = src->buf->pos; + } + } + + if (wpos == dst->buf->end) { + dst = dst->next; + if (dst) { + wpos = dst->buf->last; + } + } + } +} + + static njt_int_t njt_quic_stream_flush(njt_quic_stream_t *qs) { @@ -1095,7 +1303,13 @@ njt_quic_stream_cleanup_handler(void *data) qs = c->quic; - njt_log_debug1(NJT_LOG_DEBUG_EVENT, qs->parent->log, 0, + /* stream log was allocated from pool, now deleted */ + c->log = qs->parent->log; + c->read->log = c->log; + c->write->log = c->log; + c->pool->log = c->log; + + njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, "quic stream id:0x%xL cleanup", qs->id); if (njt_quic_shutdown_stream(c, NJT_RDWR_SHUTDOWN) != NJT_OK) { @@ -1125,6 +1339,8 @@ njt_quic_close_stream(njt_quic_stream_t *qs) njt_connection_t *pc; njt_quic_frame_t *frame; njt_quic_connection_t *qc; + njt_quic_stream_ctl_t *sctl; + njt_quic_stream_peer_t *peer; pc = qs->parent; qc = njt_quic_get_connection(pc); @@ -1167,7 +1383,12 @@ njt_quic_close_stream(njt_quic_stream_t *qs) return NJT_OK; } - if ((qs->id & NJT_QUIC_STREAM_SERVER_INITIATED) == 0) { + if (njt_quic_input_stream(qc, qs->id)) { + + peer = qc->client ? &qc->streams.server : &qc->streams.client; + + sctl = njt_quic_uni_stream(qs->id) ? &peer->uni : &peer->bidi; + frame = njt_quic_alloc_frame(pc); if (frame == NULL) { return NJT_ERROR; @@ -1176,14 +1397,8 @@ njt_quic_close_stream(njt_quic_stream_t *qs) frame->level = ssl_encryption_application; frame->type = NJT_QUIC_FT_MAX_STREAMS; - if (qs->id & NJT_QUIC_STREAM_UNIDIRECTIONAL) { - frame->u.max_streams.limit = ++qc->streams.client_max_streams_uni; - frame->u.max_streams.bidi = 0; - - } else { - frame->u.max_streams.limit = ++qc->streams.client_max_streams_bidi; - frame->u.max_streams.bidi = 1; - } + frame->u.max_streams.limit = ++sctl->max; + frame->u.max_streams.bidi = !njt_quic_uni_stream(qs->id); njt_quic_queue_frame(qc, frame); } @@ -1233,8 +1448,8 @@ njt_quic_handle_stream_frame(njt_connection_t *c, njt_quic_header_t *pkt, qc = njt_quic_get_connection(c); f = &frame->u.stream; - if ((f->stream_id & NJT_QUIC_STREAM_UNIDIRECTIONAL) - && (f->stream_id & NJT_QUIC_STREAM_SERVER_INITIATED)) + if (njt_quic_uni_stream(f->stream_id) + && njt_quic_out_stream(qc, f->stream_id)) { qc->error = NJT_QUIC_ERR_STREAM_STATE_ERROR; return NJT_ERROR; @@ -1378,9 +1593,7 @@ njt_quic_handle_stream_data_blocked_frame(njt_connection_t *c, qc = njt_quic_get_connection(c); - if ((f->id & NJT_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NJT_QUIC_STREAM_SERVER_INITIATED)) - { + if (njt_quic_uni_stream(f->id) && njt_quic_out_stream(qc, f->id)) { qc->error = NJT_QUIC_ERR_STREAM_STATE_ERROR; return NJT_ERROR; } @@ -1408,9 +1621,14 @@ njt_quic_handle_max_stream_data_frame(njt_connection_t *c, qc = njt_quic_get_connection(c); - if ((f->id & NJT_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NJT_QUIC_STREAM_SERVER_INITIATED) == 0) - { + /* + * RFC 9000, 19.10. MAX_STREAM_DATA Frames + * + * An endpoint that receives a MAX_STREAM_DATA frame for a receive-only + * stream MUST terminate the connection with error STREAM_STATE_ERROR. + */ + + if (njt_quic_uni_stream(f->id) && njt_quic_input_stream(qc, f->id)) { qc->error = NJT_QUIC_ERR_STREAM_STATE_ERROR; return NJT_ERROR; } @@ -1451,9 +1669,7 @@ njt_quic_handle_reset_stream_frame(njt_connection_t *c, qc = njt_quic_get_connection(c); - if ((f->id & NJT_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NJT_QUIC_STREAM_SERVER_INITIATED)) - { + if (njt_quic_uni_stream(f->id) && njt_quic_out_stream(qc, f->id)) { qc->error = NJT_QUIC_ERR_STREAM_STATE_ERROR; return NJT_ERROR; } @@ -1520,9 +1736,14 @@ njt_quic_handle_stop_sending_frame(njt_connection_t *c, qc = njt_quic_get_connection(c); - if ((f->id & NJT_QUIC_STREAM_UNIDIRECTIONAL) - && (f->id & NJT_QUIC_STREAM_SERVER_INITIATED) == 0) - { + /* + * RFC 9000, 19.5. STOP_SENDING Frames + * + * An endpoint that receives a STOP_SENDING frame for a receive-only + * stream MUST terminate the connection with error STREAM_STATE_ERROR. + */ + + if (njt_quic_uni_stream(f->id) && njt_quic_input_stream(qc, f->id)) { qc->error = NJT_QUIC_ERR_STREAM_STATE_ERROR; return NJT_ERROR; } @@ -1555,25 +1776,19 @@ njt_int_t njt_quic_handle_max_streams_frame(njt_connection_t *c, njt_quic_header_t *pkt, njt_quic_max_streams_frame_t *f) { + njt_quic_stream_ctl_t *sctl; njt_quic_connection_t *qc; qc = njt_quic_get_connection(c); - if (f->bidi) { - if (qc->streams.server_max_streams_bidi < f->limit) { - qc->streams.server_max_streams_bidi = f->limit; + sctl = f->bidi ? &qc->streams.server.bidi : &qc->streams.server.uni; - njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, - "quic max_streams_bidi:%uL", f->limit); - } + if (sctl->max < f->limit) { + sctl->max = f->limit; - } else { - if (qc->streams.server_max_streams_uni < f->limit) { - qc->streams.server_max_streams_uni = f->limit; - - njt_log_debug1(NJT_LOG_DEBUG_EVENT, c->log, 0, - "quic max_streams_uni:%uL", f->limit); - } + njt_log_debug2(NJT_LOG_DEBUG_EVENT, c->log, 0, + "quic max_streams_%s:%uL", + f->bidi? "bidi": "uni", f->limit); } return NJT_OK; diff --git a/src/event/quic/njt_event_quic_streams.h b/src/event/quic/njt_event_quic_streams.h index b30f791e..285906b0 100644 --- a/src/event/quic/njt_event_quic_streams.h +++ b/src/event/quic/njt_event_quic_streams.h @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -35,6 +36,8 @@ njt_int_t njt_quic_handle_max_streams_frame(njt_connection_t *c, njt_quic_header_t *pkt, njt_quic_max_streams_frame_t *f); njt_int_t njt_quic_init_streams(njt_connection_t *c); +void njt_quic_streams_init_state(njt_connection_t *c); +void njt_quic_streams_notify_write(njt_connection_t *c); void njt_quic_rbtree_insert_stream(njt_rbtree_node_t *temp, njt_rbtree_node_t *node, njt_rbtree_node_t *sentinel); njt_quic_stream_t *njt_quic_find_stream(njt_rbtree_t *rbtree, diff --git a/src/event/quic/njt_event_quic_tokens.c b/src/event/quic/njt_event_quic_tokens.c index f94b0010..b18b9ee9 100644 --- a/src/event/quic/njt_event_quic_tokens.c +++ b/src/event/quic/njt_event_quic_tokens.c @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -308,3 +309,49 @@ bad_token: return NJT_DECLINED; } + + +njt_int_t +njt_quic_verify_retry_token_integrity(njt_connection_t *c, + njt_quic_header_t *pkt) +{ + u_char *p; + size_t size; + njt_str_t ad, itag, pkt_tag; + njt_quic_connection_t *qc; + + qc = njt_quic_get_connection(c); + + /* integrity tag from retry packet */ + pkt_tag.data = pkt->data + pkt->len - NJT_QUIC_TAG_LEN; + + /* pseudo packet size */ + size = pkt->len + 1 /* od len */ + 20 /* odcid */; + + p = njt_pcalloc(c->pool, size); + if (p == NULL) { + return NJT_ERROR; + } + + ad.data = p; + + *p++ = NJT_QUIC_SERVER_CID_LEN; + p = njt_cpymem(p, qc->incid, NJT_QUIC_SERVER_CID_LEN); + p = njt_cpymem(p, pkt->data, pkt->len - NJT_QUIC_TAG_LEN); + + ad.len = p - ad.data; + + /* integrity tag to calculate using pseudo packet input */ + itag.data = ad.data + ad.len; + itag.len = NJT_QUIC_TAG_LEN; + + if (njt_quic_retry_seal(&ad, &itag, pkt->log) != NJT_OK) { + return NJT_ERROR; + } + + if (njt_memcmp(pkt_tag.data, itag.data, NJT_QUIC_TAG_LEN) != 0) { + return NJT_ERROR; + } + + return NJT_OK; +} diff --git a/src/event/quic/njt_event_quic_tokens.h b/src/event/quic/njt_event_quic_tokens.h index 61894bce..7d8f660b 100644 --- a/src/event/quic/njt_event_quic_tokens.h +++ b/src/event/quic/njt_event_quic_tokens.h @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -16,6 +17,12 @@ #define NJT_QUIC_MAX_TOKEN_SIZE 64 /* SHA-1(addr)=20 + sizeof(time_t) + retry(1) + odcid.len(1) + odcid */ +/* + * max size is not specified, this is arbitrary limit + * to match values found in the wild + */ +#define NJT_QUIC_MAX_NEW_TOKEN (NJT_QUIC_MAX_TOKEN_SIZE * 2) + #define NJT_QUIC_AES_256_GCM_IV_LEN 12 #define NJT_QUIC_AES_256_GCM_TAG_LEN 16 @@ -31,5 +38,7 @@ njt_int_t njt_quic_new_token(njt_log_t *log, struct sockaddr *sockaddr, time_t expires, njt_uint_t is_retry); njt_int_t njt_quic_validate_token(njt_connection_t *c, u_char *key, njt_quic_header_t *pkt); +njt_int_t njt_quic_verify_retry_token_integrity(njt_connection_t *c, + njt_quic_header_t *pkt); #endif /* _NJT_EVENT_QUIC_TOKENS_H_INCLUDED_ */ diff --git a/src/event/quic/njt_event_quic_transport.c b/src/event/quic/njt_event_quic_transport.c index 8c7cb99c..e02e5a30 100644 --- a/src/event/quic/njt_event_quic_transport.c +++ b/src/event/quic/njt_event_quic_transport.c @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -442,7 +443,7 @@ njt_quic_parse_long_header_v1(njt_quic_header_t *pkt) if (njt_quic_pkt_in(pkt->flags)) { - if (pkt->len < NJT_QUIC_MIN_INITIAL_SIZE) { + if (!pkt->server && pkt->len < NJT_QUIC_MIN_INITIAL_SIZE) { njt_log_error(NJT_LOG_INFO, pkt->log, 0, "quic UDP datagram is too small for initial packet"); return NJT_DECLINED; @@ -472,6 +473,29 @@ njt_quic_parse_long_header_v1(njt_quic_header_t *pkt) } else if (njt_quic_pkt_hs(pkt->flags)) { pkt->level = ssl_encryption_handshake; + } else if (njt_quic_pkt_retry(pkt->flags)) { + pkt->level = ssl_encryption_initial; + + pkt->token.len = end - p; + + if (pkt->token.len < 17) { + njt_log_error(NJT_LOG_INFO, pkt->log, 0, + "quic retry packet too small"); + return NJT_ERROR; + } + + p = njt_quic_read_bytes(p, end, pkt->token.len, &pkt->token.data); + if (p == NULL) { + njt_log_error(NJT_LOG_INFO, pkt->log, 0, + "quic packet too small to read token data"); + return NJT_ERROR; + } + + pkt->raw->pos = p; + pkt->len = p - pkt->data; + + return NJT_OK; + } else { njt_log_error(NJT_LOG_INFO, pkt->log, 0, "quic bad packet type"); @@ -620,15 +644,19 @@ static size_t njt_quic_create_long_header(njt_quic_header_t *pkt, u_char *out, u_char **pnp) { - size_t rem_len; + size_t rem_len, tlen; u_char *p, *start; + tlen = (pkt->level != ssl_encryption_initial) + ? 0 + : njt_quic_varint_len(pkt->token.len) + pkt->token.len; + rem_len = pkt->num_len + pkt->payload.len + NJT_QUIC_TAG_LEN; if (out == NULL) { return 5 + 2 + pkt->dcid.len + pkt->scid.len + njt_quic_varint_len(rem_len) + pkt->num_len - + (pkt->level == ssl_encryption_initial ? 1 : 0); + + tlen; } p = start = out; @@ -644,7 +672,11 @@ njt_quic_create_long_header(njt_quic_header_t *pkt, u_char *out, p = njt_cpymem(p, pkt->scid.data, pkt->scid.len); if (pkt->level == ssl_encryption_initial) { - njt_quic_build_int(&p, 0); + njt_quic_build_int(&p, pkt->token.len); + + if (pkt->token.len) { + p = njt_cpymem(p, pkt->token.data, pkt->token.len); + } } njt_quic_build_int(&p, rem_len); @@ -1129,6 +1161,26 @@ njt_quic_parse_frame(njt_quic_header_t *pkt, u_char *start, u_char *end, break; + case NJT_QUIC_FT_HANDSHAKE_DONE: + break; + + case NJT_QUIC_FT_NEW_TOKEN: + + p = njt_quic_parse_int(p, end, &varint); + if (p == NULL) { + goto error; + } + + f->u.token.length = varint; + + p = njt_quic_read_bytes(p, end, f->u.token.length, + &f->u.token.data); + if (p == NULL) { + goto error; + } + + break; + default: njt_log_error(NJT_LOG_INFO, pkt->log, 0, "quic unknown frame type 0x%xi", f->type); @@ -1158,43 +1210,48 @@ njt_quic_frame_allowed(njt_quic_header_t *pkt, njt_uint_t frame_type) { uint8_t ptype; +#define PKT_SRV 0x10 +#define PKT_CLN 0x20 +#define PKT_ANY 0x30 + /* * RFC 9000, 12.4. Frames and Frame Types: Table 3 * - * Frame permissions per packet: 4 bits: IH01 + * Frame permissions per packet: 6 bits: CSIH01 + * Two high bits: 'allowed on client' and 'allowed on server' */ static uint8_t njt_quic_frame_masks[] = { - /* PADDING */ 0xF, - /* PING */ 0xF, - /* ACK */ 0xD, - /* ACK_ECN */ 0xD, - /* RESET_STREAM */ 0x3, - /* STOP_SENDING */ 0x3, - /* CRYPTO */ 0xD, - /* NEW_TOKEN */ 0x0, /* only sent by server */ - /* STREAM */ 0x3, - /* STREAM1 */ 0x3, - /* STREAM2 */ 0x3, - /* STREAM3 */ 0x3, - /* STREAM4 */ 0x3, - /* STREAM5 */ 0x3, - /* STREAM6 */ 0x3, - /* STREAM7 */ 0x3, - /* MAX_DATA */ 0x3, - /* MAX_STREAM_DATA */ 0x3, - /* MAX_STREAMS */ 0x3, - /* MAX_STREAMS2 */ 0x3, - /* DATA_BLOCKED */ 0x3, - /* STREAM_DATA_BLOCKED */ 0x3, - /* STREAMS_BLOCKED */ 0x3, - /* STREAMS_BLOCKED2 */ 0x3, - /* NEW_CONNECTION_ID */ 0x3, - /* RETIRE_CONNECTION_ID */ 0x3, - /* PATH_CHALLENGE */ 0x3, - /* PATH_RESPONSE */ 0x1, - /* CONNECTION_CLOSE */ 0xF, - /* CONNECTION_CLOSE2 */ 0x3, - /* HANDSHAKE_DONE */ 0x0, /* only sent by server */ + /* PADDING */ 0xF | PKT_ANY, + /* PING */ 0xF | PKT_ANY, + /* ACK */ 0xD | PKT_ANY, + /* ACK_ECN */ 0xD | PKT_ANY, + /* RESET_STREAM */ 0x3 | PKT_ANY, + /* STOP_SENDING */ 0x3 | PKT_ANY, + /* CRYPTO */ 0xD | PKT_ANY, + /* NEW_TOKEN */ 0x0 | PKT_SRV, /* only sent by server */ + /* STREAM */ 0x3 | PKT_ANY, + /* STREAM1 */ 0x3 | PKT_ANY, + /* STREAM2 */ 0x3 | PKT_ANY, + /* STREAM3 */ 0x3 | PKT_ANY, + /* STREAM4 */ 0x3 | PKT_ANY, + /* STREAM5 */ 0x3 | PKT_ANY, + /* STREAM6 */ 0x3 | PKT_ANY, + /* STREAM7 */ 0x3 | PKT_ANY, + /* MAX_DATA */ 0x3 | PKT_ANY, + /* MAX_STREAM_DATA */ 0x3 | PKT_ANY, + /* MAX_STREAMS */ 0x3 | PKT_ANY, + /* MAX_STREAMS2 */ 0x3 | PKT_ANY, + /* DATA_BLOCKED */ 0x3 | PKT_ANY, + /* STREAM_DATA_BLOCKED */ 0x3 | PKT_ANY, + /* STREAMS_BLOCKED */ 0x3 | PKT_ANY, + /* STREAMS_BLOCKED2 */ 0x3 | PKT_ANY, + /* NEW_CONNECTION_ID */ 0x3 | PKT_ANY, + /* RETIRE_CONNECTION_ID */ 0x3 | PKT_ANY, + /* PATH_CHALLENGE */ 0x3 | PKT_ANY, + /* PATH_RESPONSE */ 0x1 | PKT_ANY, + /* CONNECTION_CLOSE */ 0xF | PKT_ANY, + /* CONNECTION_CLOSE2 */ 0x3 | PKT_ANY, + /* HANDSHAKE_DONE */ 0x0 | PKT_SRV, /* only sent by server */ }; if (njt_quic_long_pkt(pkt->flags)) { @@ -1213,6 +1270,8 @@ njt_quic_frame_allowed(njt_quic_header_t *pkt, njt_uint_t frame_type) ptype = 1; /* application data */ } + ptype |= (pkt->server ? PKT_SRV : PKT_CLN); + if (ptype & njt_quic_frame_masks[frame_type]) { return NJT_OK; } @@ -1649,7 +1708,10 @@ njt_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, } break; + case NJT_QUIC_TP_ORIGINAL_DCID: case NJT_QUIC_TP_INITIAL_SCID: + case NJT_QUIC_TP_SR_TOKEN: + case NJT_QUIC_TP_RETRY_SCID: str.len = end - p; str.data = p; @@ -1709,6 +1771,28 @@ njt_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, dst->initial_scid = str; break; + case NJT_QUIC_TP_SR_TOKEN: + + if (str.len != NJT_QUIC_SR_TOKEN_LEN) { + return NJT_ERROR; + } + njt_memcpy(dst->sr_token, str.data, NJT_QUIC_SR_TOKEN_LEN); + break; + + case NJT_QUIC_TP_ORIGINAL_DCID: + if (str.len > NJT_QUIC_CID_LEN_MAX) { + return NJT_ERROR; + } + dst->original_dcid = str; + break; + + case NJT_QUIC_TP_RETRY_SCID: + if (str.len > NJT_QUIC_CID_LEN_MAX) { + return NJT_ERROR; + } + dst->retry_scid = str; + break; + default: return NJT_ERROR; } @@ -1719,7 +1803,7 @@ njt_quic_parse_transport_param(u_char *p, u_char *end, uint16_t id, njt_int_t njt_quic_parse_transport_params(u_char *p, u_char *end, njt_quic_tp_t *tp, - njt_log_t *log) + njt_log_t *log, njt_uint_t client) { uint64_t id, len; njt_int_t rc; @@ -1737,10 +1821,12 @@ njt_quic_parse_transport_params(u_char *p, u_char *end, njt_quic_tp_t *tp, case NJT_QUIC_TP_PREFERRED_ADDRESS: case NJT_QUIC_TP_RETRY_SCID: case NJT_QUIC_TP_SR_TOKEN: - njt_log_error(NJT_LOG_INFO, log, 0, - "quic client sent forbidden transport param" - " id:0x%xL", id); - return NJT_ERROR; + if (!client) { + njt_log_error(NJT_LOG_INFO, log, 0, + "quic client sent forbidden transport param" + " id:0x%xL", id); + return NJT_ERROR; + } } p = njt_quic_parse_int(p, end, &len); @@ -1829,6 +1915,24 @@ njt_quic_parse_transport_params(u_char *p, u_char *end, njt_quic_tp_t *tp, "quic tp initial source_connection_id len:%uz %xV", tp->initial_scid.len, &tp->initial_scid); + if (client) { + njt_log_debug2(NJT_LOG_DEBUG_EVENT, log, 0, + "quic tp stateless reset token %*xs", + NJT_QUIC_SR_TOKEN_LEN, tp->sr_token); + + if (tp->original_dcid.len) { + njt_log_debug2(NJT_LOG_DEBUG_EVENT, log, 0, + "quic tp original_dcid len:%uz %xV", + tp->original_dcid.len, &tp->original_dcid); + } + + if (tp->retry_scid.len) { + njt_log_debug2(NJT_LOG_DEBUG_EVENT, log, 0, + "quic tp retry_scid len:%uz %xV", + tp->retry_scid.len, &tp->retry_scid); + } + } + return NJT_OK; } @@ -2016,7 +2120,7 @@ njt_quic_init_transport_params(njt_quic_tp_t *tp, njt_quic_conf_t *qcf) ssize_t njt_quic_create_transport_params(u_char *pos, u_char *end, njt_quic_tp_t *tp, - size_t *clen) + size_t *clen, njt_uint_t client) { u_char *p; size_t len; @@ -2087,16 +2191,20 @@ njt_quic_create_transport_params(u_char *pos, u_char *end, njt_quic_tp_t *tp, len += njt_quic_tp_len(NJT_QUIC_TP_ACK_DELAY_EXPONENT, tp->ack_delay_exponent); + if (!client) { len += njt_quic_tp_strlen(NJT_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + } len += njt_quic_tp_strlen(NJT_QUIC_TP_INITIAL_SCID, tp->initial_scid); if (tp->retry_scid.len) { len += njt_quic_tp_strlen(NJT_QUIC_TP_RETRY_SCID, tp->retry_scid); } - len += njt_quic_varint_len(NJT_QUIC_TP_SR_TOKEN); - len += njt_quic_varint_len(NJT_QUIC_SR_TOKEN_LEN); - len += NJT_QUIC_SR_TOKEN_LEN; + if (!client) { + len += njt_quic_varint_len(NJT_QUIC_TP_SR_TOKEN); + len += njt_quic_varint_len(NJT_QUIC_SR_TOKEN_LEN); + len += NJT_QUIC_SR_TOKEN_LEN; + } if (pos == NULL) { return len; @@ -2142,16 +2250,20 @@ njt_quic_create_transport_params(u_char *pos, u_char *end, njt_quic_tp_t *tp, njt_quic_tp_vint(NJT_QUIC_TP_ACK_DELAY_EXPONENT, tp->ack_delay_exponent); - njt_quic_tp_str(NJT_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + if (!client) { + njt_quic_tp_str(NJT_QUIC_TP_ORIGINAL_DCID, tp->original_dcid); + } njt_quic_tp_str(NJT_QUIC_TP_INITIAL_SCID, tp->initial_scid); if (tp->retry_scid.len) { njt_quic_tp_str(NJT_QUIC_TP_RETRY_SCID, tp->retry_scid); } - njt_quic_build_int(&p, NJT_QUIC_TP_SR_TOKEN); - njt_quic_build_int(&p, NJT_QUIC_SR_TOKEN_LEN); - p = njt_cpymem(p, tp->sr_token, NJT_QUIC_SR_TOKEN_LEN); + if (!client) { + njt_quic_build_int(&p, NJT_QUIC_TP_SR_TOKEN); + njt_quic_build_int(&p, NJT_QUIC_SR_TOKEN_LEN); + p = njt_cpymem(p, tp->sr_token, NJT_QUIC_SR_TOKEN_LEN); + } return p - pos; } diff --git a/src/event/quic/njt_event_quic_transport.h b/src/event/quic/njt_event_quic_transport.h index 1b88949a..35d7d10c 100644 --- a/src/event/quic/njt_event_quic_transport.h +++ b/src/event/quic/njt_event_quic_transport.h @@ -2,6 +2,7 @@ /* * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -168,6 +169,7 @@ typedef struct { typedef struct { uint64_t length; + u_char *data; } njt_quic_new_token_frame_t; /* @@ -338,6 +340,7 @@ typedef struct { unsigned first:1; unsigned rebound:1; unsigned path_challenged:1; + unsigned server:1; /* is from server */ } njt_quic_header_t; @@ -389,9 +392,9 @@ size_t njt_quic_create_ack_range(u_char *p, uint64_t gap, uint64_t range); njt_int_t njt_quic_init_transport_params(njt_quic_tp_t *tp, njt_quic_conf_t *qcf); njt_int_t njt_quic_parse_transport_params(u_char *p, u_char *end, - njt_quic_tp_t *tp, njt_log_t *log); + njt_quic_tp_t *tp, njt_log_t *log, njt_uint_t client); ssize_t njt_quic_create_transport_params(u_char *p, u_char *end, - njt_quic_tp_t *tp, size_t *clen); + njt_quic_tp_t *tp, size_t *clen, njt_uint_t client); void njt_quic_dcid_encode_key(u_char *dcid, uint64_t key); diff --git a/src/http/modules/njt_http_proxy_module.c b/src/http/modules/njt_http_proxy_module.c index 16eab0f7..3a6ed54d 100644 --- a/src/http/modules/njt_http_proxy_module.c +++ b/src/http/modules/njt_http_proxy_module.c @@ -4,6 +4,7 @@ * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. * Copyright (C) 2024 JD Technology Information Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -17,6 +18,10 @@ #include // #endif +#if (NJT_HTTP_V3 && NJT_QUIC_OPENSSL_COMPAT) +#include +#endif + #define NJT_HTTP_PROXY_COOKIE_SECURE 0x0001 #define NJT_HTTP_PROXY_COOKIE_SECURE_ON 0x0002 #define NJT_HTTP_PROXY_COOKIE_SECURE_OFF 0x0004 @@ -182,7 +187,79 @@ static void njt_http_v2_proxy_abort_request(njt_http_request_t *r); static void njt_http_v2_proxy_finalize_request(njt_http_request_t *r, njt_int_t rc); #endif +#if (NJT_HTTP_V3) + +/* context for creating http/3 request */ +typedef struct { + /* calculated length of request */ + size_t n; + + /* encode method state */ + njt_str_t method; + + /* encode path state */ + size_t loc_len; + size_t uri_len; + uintptr_t escape; + njt_uint_t unparsed_uri; + /* encode headers state */ + size_t max_head; + njt_http_proxy_headers_t *headers; + njt_http_script_engine_t le; + njt_http_script_engine_t e; + +} njt_http_v3_proxy_ctx_t; + + +static char *njt_http_v3_proxy_host_key(njt_conf_t *cf, njt_command_t *cmd, + void *conf); +static njt_int_t njt_http_v3_proxy_merge_quic(njt_conf_t *cf, + njt_http_proxy_loc_conf_t *conf, njt_http_proxy_loc_conf_t *prev); + +static njt_int_t njt_http_v3_proxy_create_request(njt_http_request_t *r); + +static njt_chain_t *njt_http_v3_create_headers_frame(njt_http_request_t *r, + njt_buf_t *hbuf); +static njt_chain_t *njt_http_v3_create_data_frame(njt_http_request_t *r, + njt_chain_t *body, size_t size); +static njt_inline njt_uint_t njt_http_v3_map_method(njt_uint_t method); +static njt_int_t njt_http_v3_proxy_encode_method(njt_http_request_t *r, + njt_http_v3_proxy_ctx_t *v3c, njt_buf_t *b); +static njt_int_t njt_http_v3_proxy_encode_authority(njt_http_request_t *r, + njt_http_v3_proxy_ctx_t *v3c, njt_buf_t *b); +static njt_int_t njt_http_v3_proxy_encode_path(njt_http_request_t *r, + njt_http_v3_proxy_ctx_t *v3c, njt_buf_t *b); +static njt_int_t njt_http_v3_proxy_encode_headers(njt_http_request_t *r, + njt_http_v3_proxy_ctx_t *v3c, njt_buf_t *b); +static njt_int_t njt_http_v3_proxy_body_length(njt_http_request_t *r, + njt_http_v3_proxy_ctx_t *v3c); +static njt_chain_t *njt_http_v3_proxy_encode_body(njt_http_request_t *r, + njt_http_v3_proxy_ctx_t *v3c); +static njt_int_t njt_http_v3_proxy_body_output_filter(void *data, + njt_chain_t *in); + +static njt_int_t njt_http_v3_proxy_reinit_request(njt_http_request_t *r); +static njt_int_t njt_http_v3_proxy_process_status_line(njt_http_request_t *r); +static void njt_http_v3_proxy_abort_request(njt_http_request_t *r); +static void njt_http_v3_proxy_finalize_request(njt_http_request_t *r, + njt_int_t rc); +static njt_int_t njt_http_v3_proxy_process_header(njt_http_request_t *r, + njt_str_t *name, njt_str_t *value); + +static njt_int_t njt_http_v3_proxy_headers_done(njt_http_request_t *r); +static njt_int_t njt_http_v3_proxy_process_pseudo_header(njt_http_request_t *r, + njt_str_t *name, njt_str_t *value); +static njt_int_t njt_http_v3_proxy_input_filter_init(void *data); +static njt_int_t njt_http_v3_proxy_copy_filter(njt_event_pipe_t *p, + njt_buf_t *buf); +static njt_int_t njt_http_v3_proxy_non_buffered_copy_filter(void *data, + ssize_t bytes); +static njt_int_t njt_http_v3_proxy_construct_cookie_header( + njt_http_request_t *r); + +static njt_str_t njt_http_v3_proxy_quic_salt = njt_string("njt_quic"); +#endif static njt_conf_post_t njt_http_proxy_lowat_post = { njt_http_proxy_lowat_check }; @@ -785,6 +862,53 @@ static njt_command_t njt_http_proxy_commands[] = { NULL }, #endif +#if (NJT_HTTP_V3) + + { njt_string("proxy_http3_max_concurrent_streams"), + NJT_HTTP_MAIN_CONF|NJT_HTTP_SRV_CONF|NJT_HTTP_LOC_CONF|NJT_CONF_TAKE1, + njt_conf_set_num_slot, + NJT_HTTP_LOC_CONF_OFFSET, + offsetof(njt_http_proxy_loc_conf_t, + upstream.quic.max_concurrent_streams_bidi), + NULL }, + + { njt_string("proxy_http3_stream_buffer_size"), + NJT_HTTP_MAIN_CONF|NJT_HTTP_SRV_CONF|NJT_HTTP_LOC_CONF|NJT_CONF_TAKE1, + njt_conf_set_size_slot, + NJT_HTTP_LOC_CONF_OFFSET, + offsetof(njt_http_proxy_loc_conf_t, upstream.quic.stream_buffer_size), + NULL }, + + { njt_string("proxy_quic_gso"), + NJT_HTTP_MAIN_CONF|NJT_HTTP_SRV_CONF|NJT_HTTP_LOC_CONF|NJT_CONF_FLAG, + njt_conf_set_flag_slot, + NJT_HTTP_LOC_CONF_OFFSET, + offsetof(njt_http_proxy_loc_conf_t, upstream.quic.gso_enabled), + NULL }, + + { njt_string("proxy_quic_host_key"), + NJT_HTTP_MAIN_CONF|NJT_HTTP_SRV_CONF|NJT_HTTP_LOC_CONF|NJT_CONF_TAKE1, + njt_http_v3_proxy_host_key, + NJT_HTTP_LOC_CONF_OFFSET, + 0, + NULL }, + + { njt_string("proxy_quic_active_connection_id_limit"), + NJT_HTTP_MAIN_CONF|NJT_HTTP_SRV_CONF|NJT_HTTP_LOC_CONF|NJT_CONF_TAKE1, + njt_conf_set_num_slot, + NJT_HTTP_LOC_CONF_OFFSET, + offsetof(njt_http_proxy_loc_conf_t, + upstream.quic.active_connection_id_limit), + NULL }, + + { njt_string("proxy_http3_hq"), + NJT_HTTP_MAIN_CONF|NJT_HTTP_SRV_CONF|NJT_HTTP_LOC_CONF|NJT_CONF_FLAG, + njt_conf_set_flag_slot, + NJT_HTTP_LOC_CONF_OFFSET, + offsetof(njt_http_proxy_loc_conf_t, enable_hq), + NULL }, +#endif + njt_null_command }; @@ -1077,6 +1201,29 @@ njt_http_proxy_handler(njt_http_request_t *r) u->process_header = njt_http_proxy_process_status_line; u->abort_request = njt_http_proxy_abort_request; u->finalize_request = njt_http_proxy_finalize_request; +#if (NJT_HTTP_V3) + if (plcf->http_version == NJT_HTTP_VERSION_30) { + + u->h3 = 1; + u->peer.type = SOCK_DGRAM; + + if (plcf->enable_hq) { + u->hq = 1; + + } else { + u->create_request = njt_http_v3_proxy_create_request; + u->reinit_request = njt_http_v3_proxy_reinit_request; + u->process_header = njt_http_v3_proxy_process_status_line; + u->abort_request = njt_http_v3_proxy_abort_request; + u->finalize_request = njt_http_v3_proxy_finalize_request; + } + + ctx->v3_parse = njt_pcalloc(r->pool, sizeof(njt_http_v3_parse_t)); + if (ctx->v3_parse == NULL) { + return NJT_ERROR; + } + } +#endif r->state = 0; #if (NJT_HTTP_V2) @@ -1107,10 +1254,18 @@ njt_http_proxy_handler(njt_http_request_t *r) } u->pipe->input_filter = njt_http_proxy_copy_filter; - u->pipe->input_ctx = r; u->input_filter_init = njt_http_proxy_input_filter_init; u->input_filter = njt_http_proxy_non_buffered_copy_filter; +#if (NJT_HTTP_V3) + if (plcf->http_version == NJT_HTTP_VERSION_30 && !plcf->enable_hq) { + u->pipe->input_filter = njt_http_v3_proxy_copy_filter; + + u->input_filter_init = njt_http_v3_proxy_input_filter_init; + u->input_filter = njt_http_v3_proxy_non_buffered_copy_filter; + } +#endif + u->pipe->input_ctx = r; u->input_filter_ctx = r; u->accel = 1; @@ -1168,7 +1323,13 @@ njt_http_proxy_eval(njt_http_request_t *r, njt_http_proxy_ctx_t *ctx, { add = 7; port = 80; - +#if (NJT_HTTP_V3) + if (plcf->http_version == NJT_HTTP_VERSION_30) { + njt_log_error(NJT_LOG_ERR, r->connection->log, 0, + "http/3 requires https prefix"); + return NJT_ERROR; + } +#endif #if (NJT_HTTP_SSL) } else if (proxy.len > 8 @@ -1242,6 +1403,23 @@ njt_http_proxy_eval(njt_http_request_t *r, njt_http_proxy_ctx_t *ctx, u->resolved->host = url.host; u->resolved->port = (in_port_t) (url.no_port ? port : url.port); u->resolved->no_port = url.no_port; + +#if (NJT_HTTP_V3) + if (url.family != AF_UNIX) { + + if (url.no_port) { + ctx->host = url.host; + + } else { + ctx->host.len = url.host.len + 1 + url.port_text.len; + ctx->host.data = url.host.data; + } + + } else { + njt_str_set(&ctx->host, "localhost"); + } +#endif + #if (NJT_HTTP_V3 || NJT_HTTP_V2) if (url.family != AF_UNIX) { @@ -1687,6 +1865,12 @@ njt_http_proxy_create_request(njt_http_request_t *r) u->uri.len = b->last - u->uri.data; +#if (NJT_HTTP_V3) + if (plcf->http_version == NJT_HTTP_VERSION_30 && plcf->enable_hq) { + goto nover; + } +#endif + if (plcf->http_version == NJT_HTTP_VERSION_11) { b->last = njt_cpymem(b->last, njt_http_proxy_version_11, sizeof(njt_http_proxy_version_11) - 1); @@ -1696,6 +1880,10 @@ njt_http_proxy_create_request(njt_http_request_t *r) sizeof(njt_http_proxy_version) - 1); } +#if (NJT_HTTP_V3) +nover: +#endif + njt_memzero(&e, sizeof(njt_http_script_engine_t)); e.ip = headers->values->elts; @@ -2074,6 +2262,23 @@ njt_http_proxy_process_status_line(njt_http_request_t *r) #endif +#if (NJT_HTTP_V3) + { + + njt_http_proxy_loc_conf_t *plcf; + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + + if (plcf->http_version == NJT_HTTP_VERSION_30 && plcf->enable_hq) { + r->http_version = NJT_HTTP_VERSION_9; + u->state->status = NJT_HTTP_OK; + u->headers_in.connection_close = 1; + + return NJT_OK; + } + + } +#endif njt_log_error(NJT_LOG_ERR, r->connection->log, 0, "upstream sent no valid HTTP/1.0 header"); @@ -3597,6 +3802,13 @@ njt_http_proxy_create_loc_conf(njt_conf_t *cf) * * conf->ssl_certificate = NULL; * conf->ssl_certificate_key = NULL; + * + * 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 = NJT_CONF_UNSET; @@ -3687,7 +3899,25 @@ njt_http_proxy_create_loc_conf(njt_conf_t *cf) conf->headers_hash_bucket_size = NJT_CONF_UNSET_UINT; njt_str_set(&conf->upstream.module, "proxy"); - + +#if (NJT_HTTP_V3) + + conf->upstream.quic.stream_buffer_size = NJT_CONF_UNSET_SIZE; + conf->upstream.quic.max_concurrent_streams_bidi = NJT_CONF_UNSET_UINT; + conf->upstream.quic.max_concurrent_streams_uni = + NJT_HTTP_V3_MAX_UNI_STREAMS; + conf->upstream.quic.gso_enabled = NJT_CONF_UNSET; + + conf->upstream.quic.active_connection_id_limit = NJT_CONF_UNSET_UINT; + + conf->upstream.quic.stream_close_code = NJT_HTTP_V3_ERR_NO_ERROR; + conf->upstream.quic.stream_reject_code_bidi = + NJT_HTTP_V3_ERR_REQUEST_REJECTED; + + conf->upstream.quic.shutdown = njt_http_v3_shutdown; + + conf->enable_hq = NJT_CONF_UNSET; +#endif #if (NJT_HTTP_V2) conf->upstream.h2_conf.recv_window = NJT_CONF_UNSET_SIZE; conf->upstream.h2_conf.concurrent_streams = NJT_CONF_UNSET_UINT; @@ -4140,6 +4370,16 @@ njt_http_proxy_merge_loc_conf(njt_conf_t *cf, void *parent, void *child) return NJT_CONF_ERROR; } +#if (NJT_HTTP_V3) + + if (conf->http_version == NJT_HTTP_VERSION_30) { + if (njt_http_v3_proxy_merge_quic(cf, conf, prev) != NJT_OK) { + return NJT_CONF_ERROR; + } + } + +#endif + clcf = njt_http_conf_get_module_loc_conf(cf, njt_http_core_module); if (clcf->noname @@ -5393,6 +5633,11 @@ njt_http_proxy_set_ssl(njt_conf_t *cf, njt_http_proxy_loc_conf_t *plcf) { return NJT_ERROR; } +#if (NJT_HTTP_V3 && NJT_QUIC_OPENSSL_COMPAT) + if (njt_quic_compat_init(cf, plcf->upstream.ssl->ctx) != NJT_OK) { + return NJT_ERROR; + } +#endif #if(NJT_HTTP_DYNAMIC_UPSTREAM) if(plcf->preserve == 1) { cln = njt_pool_cleanup_add(cf->cycle->pool, 0); @@ -6539,4 +6784,1937 @@ njt_http_v2_proxy_finalize_request(njt_http_request_t *r, njt_int_t rc) } #endif +#if (NJT_HTTP_V3) + +static char * +njt_http_v3_proxy_host_key(njt_conf_t *cf, njt_command_t *cmd, void *conf) +{ + njt_http_proxy_loc_conf_t *plcf = conf; + + u_char *buf; + size_t size; + ssize_t n; + njt_str_t *value; + njt_file_t file; + njt_file_info_t fi; + njt_quic_conf_t *qcf; + + qcf = &plcf->upstream.quic; + + if (qcf->host_key.len) { + return "is duplicate"; + } + + buf = NULL; +#if (NJT_SUPPRESS_WARN) + size = 0; +#endif + + value = cf->args->elts; + + if (njt_conf_full_name(cf->cycle, &value[1], 1) != NJT_OK) { + return NJT_CONF_ERROR; + } + + njt_memzero(&file, sizeof(njt_file_t)); + file.name = value[1]; + file.log = cf->log; + + file.fd = njt_open_file(file.name.data, NJT_FILE_RDONLY, NJT_FILE_OPEN, 0); + + if (file.fd == NJT_INVALID_FILE) { + njt_conf_log_error(NJT_LOG_EMERG, cf, njt_errno, + njt_open_file_n " \"%V\" failed", &file.name); + return NJT_CONF_ERROR; + } + + if (njt_fd_info(file.fd, &fi) == NJT_FILE_ERROR) { + njt_conf_log_error(NJT_LOG_CRIT, cf, njt_errno, + njt_fd_info_n " \"%V\" failed", &file.name); + goto failed; + } + + size = njt_file_size(&fi); + + if (size == 0) { + njt_conf_log_error(NJT_LOG_EMERG, cf, 0, + "\"%V\" zero key size", &file.name); + goto failed; + } + + buf = njt_pnalloc(cf->pool, size); + if (buf == NULL) { + goto failed; + } + + n = njt_read_file(&file, buf, size, 0); + + if (n == NJT_ERROR) { + njt_conf_log_error(NJT_LOG_CRIT, cf, njt_errno, + njt_read_file_n " \"%V\" failed", &file.name); + goto failed; + } + + if ((size_t) n != size) { + njt_conf_log_error(NJT_LOG_CRIT, cf, 0, + njt_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 (njt_close_file(file.fd) == NJT_FILE_ERROR) { + njt_log_error(NJT_LOG_ALERT, cf->log, njt_errno, + njt_close_file_n " \"%V\" failed", &file.name); + } + + return NJT_CONF_OK; + +failed: + + if (njt_close_file(file.fd) == NJT_FILE_ERROR) { + njt_log_error(NJT_LOG_ALERT, cf->log, njt_errno, + njt_close_file_n " \"%V\" failed", &file.name); + } + + if (buf) { + njt_explicit_memzero(buf, size); + } + + return NJT_CONF_ERROR; +} + + +static njt_int_t +njt_http_v3_proxy_merge_quic(njt_conf_t *cf, njt_http_proxy_loc_conf_t *conf, + njt_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 */ + + njt_conf_log_error(NJT_LOG_EMERG, cf, 0, + "http3 proxy requires ssl configuration " + "and https:// scheme"); + return NJT_ERROR; + } + + njt_conf_merge_value(conf->enable_hq, prev->enable_hq, 0); + + if (conf->enable_hq) { + conf->upstream.quic.alpn.data = (unsigned char *) + NJT_HTTP_V3_HQ_ALPN_PROTO; + + conf->upstream.quic.alpn.len = sizeof(NJT_HTTP_V3_HQ_ALPN_PROTO) - 1; + + } else { + conf->upstream.quic.alpn.data = (unsigned char *) + NJT_HTTP_V3_ALPN_PROTO; + + conf->upstream.quic.alpn.len = sizeof(NJT_HTTP_V3_ALPN_PROTO) - 1; + } + + njt_conf_merge_size_value(conf->upstream.quic.stream_buffer_size, + prev->upstream.quic.stream_buffer_size, + 65536); + + njt_conf_merge_uint_value(conf->upstream.quic.max_concurrent_streams_bidi, + prev->upstream.quic.max_concurrent_streams_bidi, + 128); + + njt_conf_merge_value(conf->upstream.quic.gso_enabled, + prev->upstream.quic.gso_enabled, + 0); + + njt_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 = NJT_QUIC_DEFAULT_HOST_KEY_LEN; + conf->upstream.quic.host_key.data = njt_palloc(cf->pool, + conf->upstream.quic.host_key.len); + + if (conf->upstream.quic.host_key.data == NULL) { + return NJT_ERROR; + } + + if (RAND_bytes(conf->upstream.quic.host_key.data, + NJT_QUIC_DEFAULT_HOST_KEY_LEN) + <= 0) + { + return NJT_ERROR; + } + } + + if (njt_quic_derive_key(cf->log, "av_token_key", + &conf->upstream.quic.host_key, + &njt_http_v3_proxy_quic_salt, + conf->upstream.quic.av_token_key, + NJT_QUIC_AV_KEY_LEN) + != NJT_OK) + { + return NJT_ERROR; + } + + if (njt_quic_derive_key(cf->log, "sr_token_key", + &conf->upstream.quic.host_key, + &njt_http_v3_proxy_quic_salt, + conf->upstream.quic.sr_token_key, + NJT_QUIC_SR_KEY_LEN) + != NJT_OK) + { + return NJT_ERROR; + } + + conf->upstream.quic.ssl = conf->upstream.ssl; + + return NJT_OK; +} + + +static njt_int_t +njt_http_v3_proxy_create_request(njt_http_request_t *r) +{ + njt_buf_t *b; + njt_chain_t *cl, *body, *out; + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + njt_http_v3_proxy_ctx_t v3c; + njt_http_proxy_headers_t *headers; + njt_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 = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + +#if (NJT_HTTP_CACHE) + headers = u->cacheable ? &plcf->headers_cache : &plcf->headers; +#else + headers = &plcf->headers; +#endif + + njt_memzero(&v3c, sizeof(njt_http_v3_proxy_ctx_t)); + + njt_http_script_flush_no_cacheable_variables(r, plcf->body_flushes); + njt_http_script_flush_no_cacheable_variables(r, headers->flushes); + + v3c.headers = headers; + + v3c.n = njt_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); + + /* calculate lengths */ + + njt_http_v3_proxy_encode_method(r, &v3c, NULL); + + v3c.n += njt_http_v3_encode_field_ri(NULL, 0, + NJT_HTTP_V3_HEADER_SCHEME_HTTPS); + + if (njt_http_v3_proxy_encode_path(r, &v3c, NULL) != NJT_OK) { + return NJT_ERROR; + } + + if (njt_http_v3_proxy_encode_authority(r, &v3c, NULL) != NJT_OK) { + return NJT_ERROR; + } + + if (njt_http_v3_proxy_body_length(r, &v3c) != NJT_OK) { + return NJT_ERROR; + } + + if (njt_http_v3_proxy_encode_headers(r, &v3c, NULL) != NJT_OK) { + return NJT_ERROR; + } + + /* generate HTTP/3 request of known size */ + + b = njt_create_temp_buf(r->pool, v3c.n); + if (b == NULL) { + return NJT_ERROR; + } + + b->last = (u_char *) njt_http_v3_encode_field_section_prefix(b->last, + 0, 0, 0); + + if (njt_http_v3_proxy_encode_method(r, &v3c, b) != NJT_OK) { + return NJT_ERROR; + } + + b->last = (u_char *) njt_http_v3_encode_field_ri(b->last, 0, + NJT_HTTP_V3_HEADER_SCHEME_HTTPS); + + if (njt_http_v3_proxy_encode_path(r, &v3c, b) != NJT_OK) { + return NJT_ERROR; + } + + if (njt_http_v3_proxy_encode_authority(r, &v3c, b) != NJT_OK) { + return NJT_ERROR; + } + + if (njt_http_v3_proxy_encode_headers(r, &v3c, b) != NJT_OK) { + return NJT_ERROR; + } + + out = njt_http_v3_create_headers_frame(r, b); + if (out == NJT_CHAIN_ERROR) { + return NJT_ERROR; + } + + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + + if (r->request_body_no_buffering || ctx->internal_chunked) { + u->output.output_filter = njt_http_v3_proxy_body_output_filter; + u->output.filter_ctx = r; + + } else if (ctx->internal_body_length != -1) { + + body = njt_http_v3_proxy_encode_body(r, &v3c); + if (body == NJT_CHAIN_ERROR) { + return NJT_ERROR; + } + + body = njt_http_v3_create_data_frame(r, body, + ctx->internal_body_length); + if (body == NJT_CHAIN_ERROR) { + return NJT_ERROR; + } + + for (cl = out; cl->next; cl = cl->next) { /* void */ } + cl->next = body; + } + + /* TODO: trailers */ + + u->request_bufs = out; + + return NJT_OK; +} + + +static njt_chain_t * +njt_http_v3_create_headers_frame(njt_http_request_t *r, njt_buf_t *hbuf) +{ + njt_buf_t *b; + size_t n, len; + njt_chain_t *cl, *head; + + n = hbuf->last - hbuf->pos; + + len = njt_http_v3_encode_varlen_int(NULL, NJT_HTTP_V3_FRAME_HEADERS) + + njt_http_v3_encode_varlen_int(NULL, n); + + b = njt_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + + b->last = (u_char *) njt_http_v3_encode_varlen_int(b->last, + NJT_HTTP_V3_FRAME_HEADERS); + b->last = (u_char *) njt_http_v3_encode_varlen_int(b->last, n); + + /* mark our header buffers to distinguish them in non-buffered filter */ + b->tag = (njt_buf_tag_t) &njt_http_v3_create_headers_frame; + hbuf->tag = (njt_buf_tag_t) &njt_http_v3_create_headers_frame; + + cl = njt_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + head = cl; + + cl = njt_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = hbuf; + cl->next = NULL; + + head->next = cl; + + return head; +} + + +static njt_chain_t * +njt_http_v3_create_data_frame(njt_http_request_t *r, njt_chain_t *body, + size_t size) +{ + size_t len; + njt_buf_t *b; + njt_chain_t *cl; + + len = njt_http_v3_encode_varlen_int(NULL, NJT_HTTP_V3_FRAME_DATA) + + njt_http_v3_encode_varlen_int(NULL, size); + + b = njt_create_temp_buf(r->pool, len); + if (b == NULL) { + return NJT_CHAIN_ERROR; + } + + b->last = (u_char *) njt_http_v3_encode_varlen_int(b->last, + NJT_HTTP_V3_FRAME_DATA); + b->last = (u_char *) njt_http_v3_encode_varlen_int(b->last, size); + + cl = njt_alloc_chain_link(r->pool); + if (cl == NULL) { + return NJT_CHAIN_ERROR; + } + + cl->buf = b; + cl->next = body; + + return cl; +} + + +static njt_inline njt_uint_t +njt_http_v3_map_method(njt_uint_t method) +{ + switch (method) { + case NJT_HTTP_GET: + return NJT_HTTP_V3_HEADER_METHOD_GET; + case NJT_HTTP_HEAD: + return NJT_HTTP_V3_HEADER_METHOD_HEAD; + case NJT_HTTP_POST: + return NJT_HTTP_V3_HEADER_METHOD_POST; + case NJT_HTTP_PUT: + return NJT_HTTP_V3_HEADER_METHOD_PUT; + case NJT_HTTP_DELETE: + return NJT_HTTP_V3_HEADER_METHOD_DELETE; + case NJT_HTTP_OPTIONS: + return NJT_HTTP_V3_HEADER_METHOD_OPTIONS; + default: + return 0; + } +} + + +static njt_int_t +njt_http_v3_proxy_encode_method(njt_http_request_t *r, + njt_http_v3_proxy_ctx_t *v3c, njt_buf_t *b) +{ + size_t n; + njt_str_t method; + njt_uint_t v3method; + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + njt_http_proxy_loc_conf_t *plcf; + + static njt_str_t njt_http_v3_header_method = njt_string(":method"); + + if (b == NULL) { + /* calculate length */ + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + ctx = njt_http_get_module_ctx(r, njt_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 (njt_http_complex_value(r, plcf->method, &method) != NJT_OK) { + return NJT_ERROR; + } + } else { + method = r->method_name; + } + + if (method.len == 4 + && njt_strncasecmp(method.data, (u_char *) "HEAD", 4) == 0) + { + ctx->head = 1; + } + + if (method.len) { + n = njt_http_v3_encode_field_l(NULL, &njt_http_v3_header_method, + &method); + } else { + + v3method = njt_http_v3_map_method(r->method); + + if (v3method) { + n = njt_http_v3_encode_field_ri(NULL, 0, v3method); + + } else { + n = njt_http_v3_encode_field_l(NULL, + &njt_http_v3_header_method, + &r->method_name); + } + } + + v3c->n += n; + v3c->method = method; + + return NJT_OK; + } + + method = v3c->method; + + if (method.len) { + b->last = (u_char *) njt_http_v3_encode_field_l(b->last, + &njt_http_v3_header_method, + &method); + } else { + + v3method = njt_http_v3_map_method(r->method); + + if (v3method) { + b->last = (u_char *) njt_http_v3_encode_field_ri(b->last, 0, + v3method); + } else { + b->last = (u_char *) njt_http_v3_encode_field_l(b->last, + &njt_http_v3_header_method, + &r->method_name); + } + } + + return NJT_OK; +} + + +static njt_int_t +njt_http_v3_proxy_encode_authority(njt_http_request_t *r, + njt_http_v3_proxy_ctx_t *v3c, njt_buf_t *b) +{ + size_t n; + njt_http_proxy_ctx_t *ctx; + njt_http_proxy_loc_conf_t *plcf; + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + + if (plcf->host_set) { + return NJT_OK; + } + + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + + if (b == NULL) { + + n = njt_http_v3_encode_field_lri(NULL, 0, NJT_HTTP_V3_HEADER_AUTHORITY, + NULL, ctx->host.len); + v3c->n += n; + + return NJT_OK; + } + + b->last = (u_char *) njt_http_v3_encode_field_lri(b->last, 0, + NJT_HTTP_V3_HEADER_AUTHORITY, ctx->host.data, ctx->host.len); + + njt_log_debug1(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \":authority: %V\"", &ctx->host); + + return NJT_OK; +} + + +static njt_int_t +njt_http_v3_proxy_encode_path(njt_http_request_t *r, + njt_http_v3_proxy_ctx_t *v3c, njt_buf_t *b) +{ + size_t n; + u_char *p; + size_t loc_len; + size_t uri_len; + njt_str_t tmp; + uintptr_t escape; + njt_uint_t unparsed_uri; + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + njt_http_proxy_loc_conf_t *plcf; + + static njt_str_t njt_http_v3_path = njt_string(":path"); + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + ctx = njt_http_get_module_ctx(r, njt_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 * njt_escape_uri(NULL, r->uri.data + loc_len, + r->uri.len - loc_len, + NJT_ESCAPE_URI); + } + + uri_len = ctx->vars.uri.len + r->uri.len - loc_len + escape + + sizeof("?") - 1 + r->args.len; + } + + if (uri_len == 0) { + njt_log_error(NJT_LOG_ERR, r->connection->log, 0, + "zero length URI to proxy"); + return NJT_ERROR; + } + + tmp.data = NULL; + tmp.len = uri_len; + + n = njt_http_v3_encode_field_l(NULL, &njt_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 NJT_OK; + } + + u = r->upstream; + + escape = v3c->escape; + uri_len = v3c->uri_len; + loc_len = v3c->loc_len; + unparsed_uri = v3c->unparsed_uri; + + p = njt_palloc(r->pool, uri_len); + if (p == NULL) { + return NJT_ERROR; + } + + u->uri.data = p; + + if (plcf->proxy_lengths && ctx->vars.uri.len) { + p = njt_copy(p, ctx->vars.uri.data, ctx->vars.uri.len); + + } else if (unparsed_uri) { + p = njt_copy(p, r->unparsed_uri.data, r->unparsed_uri.len); + + } else { + if (r->valid_location) { + p = njt_copy(p, ctx->vars.uri.data, ctx->vars.uri.len); + } + + if (escape) { + njt_escape_uri(p, r->uri.data + loc_len, + r->uri.len - loc_len, NJT_ESCAPE_URI); + p += r->uri.len - loc_len + escape; + + } else { + p = njt_copy(p, r->uri.data + loc_len, r->uri.len - loc_len); + } + + if (r->args.len > 0) { + *p++ = '?'; + p = njt_copy(p, r->args.data, r->args.len); + } + } + + u->uri.len = p - u->uri.data; + + b->last = (u_char *) njt_http_v3_encode_field_l(b->last, &njt_http_v3_path, + &u->uri); + return NJT_OK; +} + + +static njt_int_t +njt_http_v3_proxy_body_length(njt_http_request_t *r, + njt_http_v3_proxy_ctx_t *v3c) +{ + size_t body_len, n; + njt_http_proxy_ctx_t *ctx; + njt_http_script_engine_t *le; + njt_http_proxy_loc_conf_t *plcf; + njt_http_script_len_code_pt lcode; + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + ctx = njt_http_get_module_ctx(r, njt_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 = *(njt_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 NJT_OK; +} + + +static njt_chain_t * +njt_http_v3_proxy_encode_body(njt_http_request_t *r, + njt_http_v3_proxy_ctx_t *v3c) +{ + njt_buf_t *b; + njt_chain_t *body, *cl, *prev, *head; + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + njt_http_script_code_pt code; + njt_http_script_engine_t *e; + njt_http_proxy_loc_conf_t *plcf; + + plcf = njt_http_get_module_loc_conf(r, njt_http_proxy_module); + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + + u = r->upstream; + + /* body set in configuration */ + + if (plcf->body_values) { + + e = &v3c->e; + + cl = njt_alloc_chain_link(r->pool); + if (cl == NULL) { + return NJT_CHAIN_ERROR; + } + + b = njt_create_temp_buf(r->pool, ctx->internal_body_length); + if (b == NULL) { + return NJT_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 = *(njt_http_script_code_pt *) e->ip; + code((njt_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 = njt_alloc_buf(r->pool); + if (b == NULL) { + return NJT_CHAIN_ERROR; + } + + njt_memcpy(b, body->buf, sizeof(njt_buf_t)); + + cl = njt_alloc_chain_link(r->pool); + if (cl == NULL) { + return NJT_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 njt_int_t +njt_http_v3_proxy_body_output_filter(void *data, njt_chain_t *in) +{ + njt_http_request_t *r = data; + + off_t size; + u_char *chunk; + size_t len; + njt_buf_t *b; + njt_int_t rc; + njt_chain_t *out, *cl, *tl, **ll, **fl; + njt_http_proxy_ctx_t *ctx; + + njt_log_debug0(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "v3 proxy output filter"); + + ctx = njt_http_get_module_ctx(r, njt_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 */ + + njt_log_debug0(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "v3 proxy output header"); + + ctx->header_sent = 1; + + for ( ;; ) { + + if (in->buf->tag + != (njt_buf_tag_t) &njt_http_v3_create_headers_frame) + { + break; + } + + tl = njt_alloc_chain_link(r->pool); + if (tl == NULL) { + return NJT_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) { + njt_log_debug1(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "v3 proxy output chunk: %O", njt_buf_size(cl->buf)); + + size += njt_buf_size(cl->buf); + + if (cl->buf->flush + || cl->buf->sync + || njt_buf_in_memory(cl->buf) + || cl->buf->in_file) + { + tl = njt_alloc_chain_link(r->pool); + if (tl == NULL) { + return NJT_ERROR; + } + + tl->buf = cl->buf; + *ll = tl; + ll = &tl->next; + } + } + + if (size) { + + tl = njt_chain_get_free_buf(r->pool, &ctx->free); + if (tl == NULL) { + return NJT_ERROR; + } + + b = tl->buf; + chunk = b->start; + + if (chunk == NULL) { + len = njt_http_v3_encode_varlen_int(NULL, + NJT_HTTP_V3_FRAME_DATA) + + 8 /* max varlen int length*/; + + chunk = njt_palloc(r->pool, len); + if (chunk == NULL) { + return NJT_ERROR; + } + b->start = chunk; + b->pos = b->start; + b->end = chunk + len; + } + + b->tag = (njt_buf_tag_t) &njt_http_v3_proxy_body_output_filter; + b->memory = 0; + b->temporary = 1; + + b->last = (u_char *) njt_http_v3_encode_varlen_int(b->start, + NJT_HTTP_V3_FRAME_DATA); + b->last = (u_char *) njt_http_v3_encode_varlen_int(b->last, size); + + tl->next = *fl; + *fl = tl; + } + + *ll = NULL; + +out: + + rc = njt_chain_writer(&r->upstream->writer, out); + + njt_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, + (njt_buf_tag_t) &njt_http_v3_proxy_body_output_filter); + + return rc; +} + + +static njt_int_t +njt_http_v3_proxy_encode_headers(njt_http_request_t *r, + njt_http_v3_proxy_ctx_t *v3c, njt_buf_t *b) +{ + u_char *p, *start; + size_t key_len, val_len, hlen, max_head, n; + njt_str_t tmp, tmpv; + njt_uint_t i; + njt_list_part_t *part; + njt_table_elt_t *header; + njt_http_script_code_pt code; + njt_http_proxy_headers_t *headers; + njt_http_script_engine_t *le; + njt_http_script_engine_t *e; + njt_http_proxy_loc_conf_t *plcf; + njt_http_script_len_code_pt lcode; + + plcf = njt_http_get_module_loc_conf(r, njt_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 = *(njt_http_script_len_code_pt *) le->ip; + key_len = lcode(le); + + for (val_len = 0; *(uintptr_t *) le->ip; val_len += lcode(le)) { + lcode = *(njt_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 += njt_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 (njt_hash_find(&headers->hash, header[i].hash, + header[i].lowcase_key, header[i].key.len)) + { + continue; + } + + n += njt_http_v3_encode_field_l(NULL, &header[i].key, + &header[i].value); + } + } + + v3c->n += n; + v3c->max_head = max_head; + + return NJT_OK; + } + + max_head = v3c->max_head; + + p = njt_pnalloc(r->pool, max_head); + if (p == NULL) { + return NJT_ERROR; + } + + start = p; + + njt_memzero(e, sizeof(njt_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 = *(njt_http_script_len_code_pt *) le->ip; + (void) lcode(le); + + for (val_len = 0; *(uintptr_t *) le->ip; val_len += lcode(le)) { + lcode = *(njt_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 = *(njt_http_script_code_pt *) e->ip; + code((njt_http_script_engine_t *) e); + } + e->ip += sizeof(uintptr_t); + + e->skip = 0; + + continue; + } + + code = *(njt_http_script_code_pt *) e->ip; + code((njt_http_script_engine_t *) e); + + tmp.len = e->pos - tmp.data; + tmpv.data = e->pos; + + while (*(uintptr_t *) e->ip) { + code = *(njt_http_script_code_pt *) e->ip; + code((njt_http_script_engine_t *) e); + } + e->ip += sizeof(uintptr_t); + + tmpv.len = e->pos - tmpv.data; + + b->last = (u_char *) njt_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 (njt_hash_find(&headers->hash, header[i].hash, + header[i].lowcase_key, header[i].key.len)) + { + continue; + } + + b->last = (u_char *) njt_http_v3_encode_field_l(b->last, + &header[i].key, + &header[i].value); + + njt_log_debug2(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "http proxy header: \"%V: %V\"", + &header[i].key, &header[i].value); + } + } + + return NJT_OK; +} + + +static njt_int_t +njt_http_v3_proxy_reinit_request(njt_http_request_t *r) +{ + njt_http_proxy_ctx_t *ctx; + + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + + if (ctx == NULL) { + return NJT_OK; + } + + r->upstream->process_header = njt_http_v3_proxy_process_status_line; + r->upstream->pipe->input_filter = njt_http_v3_proxy_copy_filter; + r->upstream->input_filter = njt_http_v3_proxy_non_buffered_copy_filter; + r->state = 0; + + return NJT_OK; +} + + +static njt_int_t +njt_http_v3_proxy_process_status_line(njt_http_request_t *r) +{ + u_char *p; + njt_buf_t *b; + njt_int_t rc; + njt_connection_t *c, stub; + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + njt_http_v3_session_t *h3c; + njt_http_v3_parse_headers_t *st; + + u = r->upstream; + c = u->peer.connection; + + njt_log_debug0(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "njt_http_v3_proxy_process_status_line"); + + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + if (ctx == NULL) { + return NJT_ERROR; + } + +#if (NJT_HTTP_CACHE) + if (r->cache) { + /* no connection here */ + h3c = NULL; + njt_memzero(&stub, sizeof(njt_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 = njt_http_v3_get_session(c); + + if (njt_list_init(&u->headers_in.headers, r->pool, 20, + sizeof(njt_table_elt_t)) + != NJT_OK) + { + return NJT_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 = njt_http_v3_parse_headers(c, st, b); + if (rc > 0) { + + if (h3c) { + njt_quic_reset_stream(c, rc); + } + njt_log_error(NJT_LOG_ERR, r->connection->log, 0, + "upstream sent invalid header rc:%i", rc); + return NJT_HTTP_UPSTREAM_INVALID_HEADER; + } + + if (rc == NJT_ERROR) { + return NJT_ERROR; + } + + if (h3c) { + h3c->total_bytes += b->pos - p; + } + + if (rc == NJT_BUSY) { + /* HTTP/3 blocked */ + return NJT_AGAIN; + } + + if (rc == NJT_AGAIN) { + return NJT_AGAIN; + } + + /* rc == NJT_OK || rc == njt_DONE */ + + if (h3c) { + h3c->payload_bytes += njt_http_v3_encode_field_l(NULL, + &st->field_rep.field.name, + &st->field_rep.field.value); + } + + if (njt_http_v3_proxy_process_header(r, &st->field_rep.field.name, + &st->field_rep.field.value) + != NJT_OK) + { + return NJT_ERROR; + } + + if (rc == NJT_DONE) { + return njt_http_v3_proxy_headers_done(r); + } + } + + return NJT_OK; +} + + +static void +njt_http_v3_proxy_abort_request(njt_http_request_t *r) +{ + njt_log_debug0(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "abort http v3 proxy request"); +} + + +static void +njt_http_v3_proxy_finalize_request(njt_http_request_t *r, njt_int_t rc) +{ + njt_log_debug0(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "finalize http v3 proxy request"); +} + + +static njt_int_t +njt_http_v3_proxy_process_header(njt_http_request_t *r, njt_str_t *name, + njt_str_t *value) +{ + size_t len; + njt_table_elt_t *h; + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + njt_http_upstream_header_t *hh; + njt_http_upstream_main_conf_t *umcf; + + /* based on njt_http_v3_process_header() */ + + umcf = njt_http_get_module_main_conf(r, njt_http_upstream_module); + u = r->upstream; + + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + + len = name->len + value->len; + + if (len > ctx->v3_parse->header_limit) { + njt_log_error(NJT_LOG_INFO, r->connection->log, 0, + "client sent too large header"); + return NJT_ERROR; + } + + ctx->v3_parse->header_limit -= len; + + if (name->len && name->data[0] == ':') { + return njt_http_v3_proxy_process_pseudo_header(r, name, value); + } + + h = njt_list_push(&u->headers_in.headers); + if (h == NULL) { + return NJT_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 = njt_pnalloc(r->pool, name->len + 1); + if (h->key.data == NULL) { + return NJT_ERROR; + } + njt_memcpy(h->key.data, name->data, name->len); + h->key.data[h->key.len] = 0; + + h->value.len = value->len; + h->value.data = njt_pnalloc(r->pool, value->len + 1); + if (h->value.data == NULL) { + return NJT_ERROR; + } + njt_memcpy(h->value.data, value->data, value->len); + h->value.data[h->value.len] = 0; + + h->lowcase_key = h->key.data; + h->hash = njt_hash_key(h->key.data, h->key.len); + + hh = njt_hash_find(&umcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NJT_OK) { + return NJT_ERROR; + } + + njt_log_debug2(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \"%V: %V\"", name, value); + + return NJT_OK; +} + + +static njt_int_t +njt_http_v3_proxy_headers_done(njt_http_request_t *r) +{ + njt_table_elt_t *h; + njt_connection_t *c; + njt_http_proxy_ctx_t *ctx; + njt_http_upstream_t *u; + + /* + * based on NJT_HTTP_PARSE_HEADER_DONE in njt_http_proxy_process_header() + * and njt_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 = njt_list_push(&u->headers_in.headers); + if (h == NULL) { + return NJT_ERROR; + } + + h->hash = njt_hash(njt_hash(njt_hash(njt_hash( + njt_hash('s', 'e'), 'r'), 'v'), 'e'), 'r'); + + njt_str_set(&h->key, "Server"); + njt_str_null(&h->value); + h->lowcase_key = (u_char *) "server"; + h->next = NULL; + } + + if (u->headers_in.date == NULL) { + h = njt_list_push(&u->headers_in.headers); + if (h == NULL) { + return NJT_ERROR; + } + + h->hash = njt_hash(njt_hash(njt_hash('d', 'a'), 't'), 'e'); + + njt_str_set(&h->key, "Date"); + njt_str_null(&h->value); + h->lowcase_key = (u_char *) "date"; + h->next = NULL; + } + + if (njt_http_v3_proxy_construct_cookie_header(r) != NJT_OK) { + return NJT_ERROR; + } + + if (u->headers_in.content_length) { + u->headers_in.content_length_n = + njt_atoof(u->headers_in.content_length->value.data, + u->headers_in.content_length->value.len); + + if (u->headers_in.content_length_n == NJT_ERROR) { + njt_log_error(NJT_LOG_INFO, c->log, 0, + "client sent invalid \"Content-Length\" header"); + return NJT_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 = njt_http_get_module_ctx(r, njt_http_proxy_module); + + if (u->headers_in.status_n == NJT_HTTP_NO_CONTENT + || u->headers_in.status_n == NJT_HTTP_NOT_MODIFIED + || ctx->head + || (!u->headers_in.chunked + && u->headers_in.content_length_n == 0)) + { + u->keepalive = !u->headers_in.connection_close; + } + + return NJT_OK; +} + + +static njt_int_t +njt_http_v3_proxy_process_pseudo_header(njt_http_request_t *r, njt_str_t *name, + njt_str_t *value) +{ + njt_int_t status; + njt_str_t *status_line; + njt_http_upstream_t *u; + + /* based on njt_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 && njt_strncmp(name->data, ":status", 7) == 0) { + + if (u->state && u->state->status +#if (NJT_HTTP_CACHE) + && !r->cached +#endif + ) { + njt_log_error(NJT_LOG_INFO, r->connection->log, 0, + "upstream sent duplicate \":status\" header"); + return NJT_ERROR; + } + + if (value->len == 0) { + njt_log_error(NJT_LOG_INFO, r->connection->log, 0, + "upstream sent empty \":status\" header"); + return NJT_ERROR; + } + + if (value->len < 3) { + njt_log_error(NJT_LOG_INFO, r->connection->log, 0, + "upstream sent too short \":status\" header"); + return NJT_ERROR; + } + + status = njt_atoi(value->data, 3); + + if (status == NJT_ERROR) { + njt_log_error(NJT_LOG_ERR, r->connection->log, 0, + "upstream sent invalid status \"%V\"", value); + return NJT_ERROR; + } + + if (u->state && u->state->status == 0) { + u->state->status = status; + } + + u->headers_in.status_n = status; + + status_line = njt_http_status_line(status); + if (status_line) { + u->headers_in.status_line = *status_line; + } + + njt_log_debug2(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "http v3 proxy status %ui \"%V\"", + u->headers_in.status_n, &u->headers_in.status_line); + + return NJT_OK; + } + + njt_log_error(NJT_LOG_INFO, r->connection->log, 0, + "upstream sent unexpected pseudo-header \"%V\"", name); + + return NJT_ERROR; +} + + +static njt_int_t +njt_http_v3_proxy_input_filter_init(void *data) +{ + njt_http_request_t *r = data; + + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + + u = r->upstream; + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + + if (ctx == NULL) { + return NJT_ERROR; + } + + njt_log_debug4(NJT_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 == NJT_HTTP_NO_CONTENT + || u->headers_in.status_n == NJT_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 NJT_OK; +} + + +/* reading non-buffered body from V3 upstream */ +static njt_int_t +njt_http_v3_proxy_non_buffered_copy_filter(void *data, ssize_t bytes) +{ + njt_http_request_t *r = data; + + size_t size, len; + njt_int_t rc; + njt_buf_t *b, *buf; + njt_chain_t *cl, **ll; + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + njt_http_v3_parse_data_t *st; + + njt_log_debug0(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "http v3 proxy non buffered copy filter"); + + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + + if (ctx == NULL) { + return NJT_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 = njt_http_v3_parse_data(r->connection, st, buf); + + njt_log_debug2(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "njt_http_v3_parse_data rc:%i st->length: %ui", + rc, st->length); + + if (rc == NJT_AGAIN) { + break; + } + + if (rc == NJT_ERROR || rc > 0) { + return NJT_ERROR; + } + + if (rc == NJT_DONE) { + /* TODO: trailers */ + u->length = 0; + } + + /* rc == NJT_OK */ + continue; + } + + /* need to consume ctx->st.length bytes and then parse again */ + + cl = njt_chain_get_free_buf(r->pool, &u->free_bufs); + if (cl == NULL) { + return NJT_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 = njt_min(size, st->length); + + buf->pos += len; + st->length -= len; + + if (u->length != -1) { + u->length -= len; + } + + b->last = buf->pos; + + njt_log_debug2(NJT_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 NJT_OK; +} + + +static njt_int_t +njt_http_v3_proxy_copy_filter(njt_event_pipe_t *p, njt_buf_t *buf) +{ + size_t size, len; + njt_int_t rc; + njt_buf_t *b, **prev; + njt_chain_t *cl; + njt_http_upstream_t *u; + njt_http_request_t *r; + njt_http_proxy_ctx_t *ctx; + njt_http_v3_parse_data_t *st; + + njt_log_debug0(NJT_LOG_DEBUG_HTTP, p->log, 0, + "http_v3_proxy_copy_filter"); + + if (buf->pos == buf->last) { + return NJT_OK; + } + + if (p->upstream_done) { + njt_log_debug0(NJT_LOG_DEBUG_HTTP, p->log, 0, + "http v3 proxy data after close"); + return NJT_OK; + } + + if (p->length == 0) { + njt_log_error(NJT_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 NJT_OK; + } + + r = p->input_ctx; + u = r->upstream; + + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + if (ctx == NULL) { + return NJT_ERROR; + } + + st = &ctx->v3_parse->body; + + b = NULL; + prev = &buf->shadow; + + while (buf->pos < buf->last) { + + if (st->length == 0) { + rc = njt_http_v3_parse_data(r->connection, st, buf); + + njt_log_debug2(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, + "njt_http_v3_parse_data rc:%i st->length: %ui", + rc, st->length); + + if (rc == NJT_AGAIN) { + break; + } + + if (rc == NJT_ERROR || rc > 0) { + return NJT_ERROR; + } + + if (rc == NJT_DONE) { + /* TODO: trailers */ + p->length = 0; + } + + /* rc == NJT_OK */ + continue; + } + + /* need to consume ctx->st.length bytes and then parse again */ + + cl = njt_chain_get_free_buf(p->pool, &p->free); + if (cl == NULL) { + return NJT_ERROR; + } + + b = cl->buf; + + njt_memcpy(b, buf, sizeof(njt_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 = njt_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; + } + + njt_log_debug2(NJT_LOG_DEBUG_EVENT, p->log, 0, + "http v3 proxy input buf #%d %p", b->num, b->pos); + } + + njt_log_debug2(NJT_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; + + njt_log_debug2(NJT_LOG_DEBUG_EVENT, p->log, 0, + "input buf %p %z", b->pos, b->last - b->pos); + return NJT_OK; + } + + /* there is no data record in the buf, add it to free chain */ + + if (njt_event_pipe_add_free_buf(p, buf) != NJT_OK) { + return NJT_ERROR; + } + + return NJT_OK; +} + + +static njt_int_t +njt_http_v3_proxy_construct_cookie_header(njt_http_request_t *r) +{ + u_char *buf, *p, *end; + size_t len; + njt_str_t *vals; + njt_uint_t i; + njt_array_t *cookies; + njt_table_elt_t *h; + njt_http_header_t *hh; + njt_http_upstream_t *u; + njt_http_proxy_ctx_t *ctx; + njt_http_upstream_main_conf_t *umcf; + + static njt_str_t cookie = njt_string("cookie"); + + ctx = njt_http_get_module_ctx(r, njt_http_proxy_module); + + u = r->upstream; + cookies = ctx->v3_parse->cookies; + + if (cookies == NULL) { + return NJT_OK; + } + + vals = cookies->elts; + + i = 0; + len = 0; + + do { + len += vals[i].len + 2; + } while (++i != cookies->nelts); + + len -= 2; + + buf = njt_pnalloc(r->pool, len + 1); + if (buf == NULL) { + return NJT_ERROR; + } + + p = buf; + end = buf + len; + + for (i = 0; /* void */ ; i++) { + + p = njt_cpymem(p, vals[i].data, vals[i].len); + + if (p == end) { + *p = '\0'; + break; + } + + *p++ = ';'; *p++ = ' '; + } + + h = njt_list_push(&u->headers_in.headers); + if (h == NULL) { + return NJT_ERROR; + } + + h->hash = njt_hash(njt_hash(njt_hash(njt_hash( + njt_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 = njt_http_get_module_main_conf(r, njt_http_upstream_module); + + hh = njt_hash_find(&umcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + return NJT_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NJT_OK) { + return NJT_ERROR; + } + + return NJT_OK; +} +#endif \ No newline at end of file diff --git a/src/http/modules/njt_http_proxy_module.h b/src/http/modules/njt_http_proxy_module.h index c18949aa..01ac2088 100644 --- a/src/http/modules/njt_http_proxy_module.h +++ b/src/http/modules/njt_http_proxy_module.h @@ -3,6 +3,7 @@ * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ #ifndef _NJT_HTTP_PROXY_H_INCLUDED_ @@ -134,6 +135,9 @@ typedef struct { njt_str_t host; njt_uint_t host_set; #endif +#if (NJT_HTTP_V3) + njt_flag_t enable_hq; +#endif } njt_http_proxy_loc_conf_t; @@ -149,6 +153,12 @@ typedef struct { #if (NJT_HTTP_V3 || NJT_HTTP_V2) njt_str_t host; #endif + +#if (NJT_HTTP_V3) + njt_http_v3_parse_t *v3_parse; + size_t data_recvd; +#endif + unsigned head:1; unsigned internal_chunked:1; unsigned header_sent:1; diff --git a/src/http/modules/njt_http_upstream_keepalive_module.c b/src/http/modules/njt_http_upstream_keepalive_module.c index bdbb8c49..09477d40 100644 --- a/src/http/modules/njt_http_upstream_keepalive_module.c +++ b/src/http/modules/njt_http_upstream_keepalive_module.c @@ -3,6 +3,7 @@ * Copyright (C) Maxim Dounin * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -294,6 +295,18 @@ found: return NJT_DONE; } #endif + +#if (NJT_HTTP_V3) + if (c->quic) { + pc->connection = c->quic->parent; + pc->cached = 1; + + njt_http_v3_upstream_close_request_stream(c, 1); + + return NJT_DONE; + } +#endif + c->idle = 0; c->sent = 0; c->data = NULL; @@ -506,6 +519,17 @@ njt_http_upstream_keepalive_close(njt_connection_t *c) return; } #endif +#if (NJT_HTTP_V3) + njt_connection_t *pc; + + if (c->quic) { + pc = c->quic->parent; + njt_http_v3_upstream_close_request_stream(c, 1); + njt_http_v3_shutdown(pc); + return; + } +#endif + #if (NJT_HTTP_SSL) if (c->ssl) { diff --git a/src/http/njt_http_request.h b/src/http/njt_http_request.h index 901129dc..9f3c885f 100644 --- a/src/http/njt_http_request.h +++ b/src/http/njt_http_request.h @@ -3,6 +3,7 @@ * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ diff --git a/src/http/njt_http_upstream.c b/src/http/njt_http_upstream.c index 245c975e..31350746 100644 --- a/src/http/njt_http_upstream.c +++ b/src/http/njt_http_upstream.c @@ -3,6 +3,7 @@ * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC * Copyright (C) 2024 JD Technology Information Technology Co., Ltd. */ @@ -221,6 +222,23 @@ njt_int_t njt_http_v2_filter_send(njt_connection_t *fc, void njt_http_v2_filter_cleanup(void *data); #endif +#if (NJT_HTTP_V3) +static njt_int_t njt_http_v3_upstream_init_connection(njt_http_request_t *, + njt_http_upstream_t *u, njt_connection_t *c); +static njt_int_t njt_http_v3_upstream_init_ssl(njt_connection_t *c, void *data); +static njt_int_t njt_http_v3_upstream_reuse_connection(njt_http_request_t *r, + njt_http_upstream_t *u, njt_connection_t *c); +static njt_int_t njt_http_v3_upstream_init_h3(njt_connection_t *c, + njt_http_request_t *r); +static void njt_http_v3_upstream_connect_handler(njt_event_t *ev); +static njt_int_t njt_http_v3_upstream_connected(njt_http_request_t *r, + njt_http_upstream_t *u, njt_connection_t *sc); +static njt_int_t njt_http_v3_upstream_send_request(njt_http_request_t *r, + njt_http_upstream_t *u, njt_connection_t *sc); +static void njt_http_quic_upstream_dummy_handler(njt_event_t *ev); +static void njt_http_quic_stream_close_handler(njt_event_t *ev); +#endif + static njt_http_upstream_header_t njt_http_upstream_headers_in[] = { { njt_string("Status"), @@ -1707,6 +1725,20 @@ njt_http_upstream_connect(njt_http_request_t *r, njt_http_upstream_t *u) } } #endif +#if (NJT_HTTP_V3) + + /* this is cached main quic connection with completed handshake */ + if (u->peer.cached && c->type == SOCK_DGRAM) { + if (njt_http_v3_upstream_reuse_connection(r, u, c) != NJT_OK) { + njt_http_upstream_finalize_request(r, u, + NJT_HTTP_INTERNAL_SERVER_ERROR); + } + + return; + } + +#endif + c->data = r; c->write->handler = njt_http_upstream_handler; @@ -1747,6 +1779,26 @@ njt_http_upstream_connect(njt_http_request_t *r, njt_http_upstream_t *u) return; } +#if (NJT_HTTP_V3) + + if (u->h3) { + rc = njt_http_v3_upstream_init_connection(r, u, c); + + if (rc == NJT_DECLINED) { + njt_http_upstream_next(r, u, NJT_HTTP_UPSTREAM_FT_ERROR); + return; + } + + if (rc != NJT_OK) { + njt_http_upstream_finalize_request(r, u, + NJT_HTTP_INTERNAL_SERVER_ERROR); + } + + return; + } + +#endif + #if (NJT_HTTP_SSL) if (u->ssl && c->ssl == NULL) { @@ -2052,6 +2104,17 @@ njt_http_upstream_ssl_save_session(njt_connection_t *c) njt_http_request_t *r; njt_http_upstream_t *u; +#if (NJT_HTTP_V3) + if (c->udp) { + /* SSL callback is called on main quic connection */ + c = njt_quic_client_get_ssl_data(c); + if (c == NULL) { + /* stream already closed */ + return; + } + } +#endif + if (c->idle) { return; } @@ -2466,6 +2529,22 @@ njt_http_upstream_send_request(njt_http_request_t *r, njt_http_upstream_t *u, if (!u->request_body_sent) { u->request_body_sent = 1; +#if (NJT_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 (njt_quic_shutdown_stream(c, NJT_WRITE_SHUTDOWN) != NJT_OK) { + njt_http_upstream_finalize_request(r, u, + NJT_HTTP_INTERNAL_SERVER_ERROR); + return; + } + } +#endif + if (u->header_sent) { return; } @@ -2643,6 +2722,42 @@ njt_http_upstream_send_request_handler(njt_http_request_t *r, return; } +#if (NJT_HTTP_V3) + + if (u->h3) { + if (!u->h3_started) { + + rc = njt_http_v3_upstream_connected(r, u, c); + + if (rc == NJT_DECLINED) { + njt_http_upstream_next(r, u, NJT_HTTP_UPSTREAM_FT_ERROR); + return; + } + + if (rc != NJT_OK) { + njt_http_upstream_finalize_request(r, u, + NJT_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + } else { + + if (u->header_sent && !u->conf->preserve_output) { + u->write_event_handler = njt_http_upstream_dummy_handler; + + (void) njt_handle_write_event(c->write, 0); + + return; + } + + njt_http_upstream_send_request(r, u, 1); + } + + return; + } + +#endif + #if (NJT_HTTP_SSL) if (u->ssl && c->ssl == NULL) { @@ -4795,6 +4910,9 @@ njt_http_upstream_close_peer_connection(njt_http_request_t *r, { njt_pool_t *pool; njt_connection_t *c; +#if (NJT_HTTP_V3) + njt_connection_t *sc; +#endif c = u->peer.connection; @@ -4812,6 +4930,47 @@ njt_http_upstream_close_peer_connection(njt_http_request_t *r, } #endif +#if (NJT_HTTP_V3) + if (c->type == SOCK_DGRAM || c->quic) { + + if (c->quic) { + /* a quic stream */ + + sc = c; + c = c->quic->parent; + + njt_http_v3_upstream_close_request_stream(sc, 1); + + if (u->h3_started && !u->hq) { + /* HTTP/3 was initialized on this stream, close gracefully */ + njt_http_v3_shutdown(c); + } + } + + if (c->udp) { + /* main QUIC udp connection */ + njt_quic_finalize_connection(c, 0 /* njt_QUIC_ERR_NO_ERROR */, ""); + + } else { + /* + * early error, we failed to create quic connection object, + * cleanup normal connection created by upstream + */ + pool = c->pool; + + njt_close_connection(c); + + if (pool) { + njt_destroy_pool(pool); + } + } + + u->peer.connection = NULL; + + return; + } +#endif + #if (NJT_HTTP_SSL) if (c->ssl) { c->ssl->no_wait_shutdown = 1; @@ -4890,8 +5049,23 @@ njt_http_upstream_finalize_request(njt_http_request_t *r, } if (u->peer.connection) { +#if (NJT_HTTP_V3) + /* TODO: do not shutdown persistent connection */ + + /* + * We send the "close notify" shutdown alert to the upstream only + * and do not wait its "close notify" shutdown alert. + * It is acceptable according to the TLS standard. + */ + if (u->h3){ + njt_http_upstream_close_peer_connection(r, u, 0); + }else{ + njt_http_upstream_close_peer_connection(r, u, 1); + } +#else njt_http_upstream_close_peer_connection(r, u, 1); - } +#endif + } if (u->pipe && u->pipe->temp_file) { njt_log_debug1(NJT_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -6913,6 +7087,405 @@ njt_http_upstream_bind_set_slot(njt_conf_t *cf, njt_command_t *cmd, return NJT_CONF_OK; } +#if (NJT_HTTP_V3) + +static njt_int_t +njt_http_v3_upstream_init_connection(njt_http_request_t *r, + njt_http_upstream_t *u, njt_connection_t *c) +{ + njt_int_t rc; + njt_connection_t *sc; + + njt_log_debug1(NJT_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 = njt_pnalloc(c->pool, u->peer.name->len); + if (c->addr_text.data == NULL) { + return NJT_ERROR; + } + + njt_memcpy(c->addr_text.data, u->peer.name->data, u->peer.name->len); + + if (njt_quic_create_client(&u->conf->quic, c) != NJT_OK) { + return NJT_ERROR; + } + + c->listening->handler = njt_http_v3_init_client_stream; + + r->connection->log->action = "QUIC handshaking to upstream"; + + if (njt_http_v3_upstream_init_h3(c, r) != NJT_OK) { + return NJT_ERROR; + } + + sc = njt_quic_open_stream(c, 1); + if (sc == NULL) { + return NJT_ERROR; + } + + sc->data = r; + + /* + * main quic connection lives own life, we will acess it via stream + * when required + */ + u->peer.connection = sc; + + njt_log_debug1(NJT_LOG_DEBUG_HTTP, sc->log, 0, + "http3 client bidi stream created sc:%p", sc); + + rc = njt_quic_connect(c, njt_http_v3_upstream_init_ssl, sc); + + if (rc == NJT_AGAIN) { + njt_add_timer(sc->write, u->conf->connect_timeout); + sc->write->handler = njt_http_v3_upstream_connect_handler; + sc->read->handler = njt_http_quic_stream_close_handler; + + return NJT_OK; + } + + if (rc != NJT_OK) { + return NJT_ERROR; + } + + return njt_http_v3_upstream_connected(r, u, sc); +} + + +static njt_int_t +njt_http_v3_upstream_init_ssl(njt_connection_t *c, void *data) +{ + njt_connection_t *sc = data; + + njt_str_t *alpn; + njt_http_request_t *r; + njt_http_upstream_t *u; + + if (sc == NULL) { + njt_log_error(NJT_LOG_INFO, c->log, 0, + "http3 stream cannot be reused"); + return NJT_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) + { + njt_log_error(NJT_LOG_INFO, c->log, 0, + "http3 SSL_set_alpn_protos() failed"); + return NJT_ERROR; + } + + if (u->conf->ssl_server_name || u->conf->ssl_verify) { + if (njt_http_upstream_ssl_name(r, u, c) != NJT_OK) { + return NJT_ERROR; + } + } + +#if (NJT_HTTP_MULTICERT) + if (u->conf->ssl_certificate_values) { + if (njt_http_upstream_ssl_certificates(r, u, c) != NJT_OK) { + njt_http_upstream_finalize_request(r, u, + NJT_HTTP_INTERNAL_SERVER_ERROR); + return NJT_ERROR; + } + + } else +#endif + if (u->conf->ssl_certificate + && u->conf->ssl_certificate->value.len + && (u->conf->ssl_certificate->lengths + || u->conf->ssl_certificate_key->lengths)) + { + if (njt_http_upstream_ssl_certificate(r, u, c) != NJT_OK) { + return NJT_ERROR; + } + } + + if (u->conf->ssl_session_reuse) { + c->ssl->save_session = njt_http_upstream_ssl_save_session; + + if (u->peer.set_session(&u->peer, u->peer.data) != NJT_OK) { + return NJT_ERROR; + } + } + + return NJT_OK; +} + + +static njt_int_t +njt_http_v3_upstream_reuse_connection(njt_http_request_t *r, + njt_http_upstream_t *u, njt_connection_t *c) +{ + njt_connection_t *sc; + + njt_log_debug1(NJT_LOG_DEBUG_HTTP, c->log, 0, + "http3 upstream reuse connection c:%p", c); + + if (njt_http_upstream_configure(r, u, c) != NJT_OK) { + return NJT_ERROR; + } + + sc = njt_quic_open_stream(c, 1); + if (sc == NULL) { + return NJT_ERROR; + } + + sc->data = r; + u->peer.connection = sc; + + njt_log_debug1(NJT_LOG_DEBUG_HTTP, sc->log, 0, + "http3 client bidi stream created sc:%p", sc); + + return njt_http_v3_upstream_send_request(r, u, sc); +} + + +static njt_int_t +njt_http_v3_upstream_init_h3(njt_connection_t *c, njt_http_request_t *r) +{ + njt_http_v3_session_t *h3c; + njt_http_log_ctx_t *ctx; + njt_http_connection_t *hc; + njt_http_core_srv_conf_t *cscf; + + cscf = njt_http_get_module_srv_conf(r, njt_http_core_module); + + hc = njt_pcalloc(c->pool, sizeof(njt_http_connection_t)); + if (hc == NULL) { + return NJT_ERROR; + } + + hc->ssl = 1; + + c->data = hc; + + /* hc->addr_conf is unused */ + hc->conf_ctx = cscf->ctx; /* needed for streams to get config */ + + ctx = njt_palloc(c->pool, sizeof(njt_http_log_ctx_t)); + if (ctx == NULL) { + return NJT_ERROR; + } + + ctx->connection = c; + ctx->request = NULL; + ctx->current_request = NULL; + + c->log_error = NJT_ERROR_INFO; + + if (njt_http_v3_init_session(c) != NJT_OK) { + return NJT_ERROR; + } + + h3c = njt_http_v3_get_session(c); + + h3c->client = 1; + + return NJT_OK; +} + + +static void +njt_http_v3_upstream_connect_handler(njt_event_t *ev) +{ + njt_uint_t ft_type; + njt_connection_t *c, *sc; + njt_http_request_t *r; + njt_http_upstream_t *u; + + njt_log_debug0(NJT_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 = NJT_HTTP_UPSTREAM_FT_TIMEOUT; + njt_connection_error(c, NJT_ETIMEDOUT, "http3 connection timed out"); + goto next; + } + + if (ev->error) { + njt_connection_error(c, 0, "http3 connection error"); + ft_type = NJT_HTTP_UPSTREAM_FT_ERROR; + goto next; + } + + if (ev->closed) { + njt_connection_error(c, 0, "http3 connection was closed"); + ft_type = NJT_HTTP_UPSTREAM_FT_ERROR; + goto next; + } + + njt_http_set_log_request(c->log, r); + + if (njt_http_v3_upstream_connected(r, u, sc) != NJT_OK) { + ft_type = NJT_HTTP_UPSTREAM_FT_ERROR; + goto next; + } + + njt_http_run_posted_requests(c); + + return; + +next: + + njt_http_upstream_next(r, u, ft_type); +} + + +static njt_int_t +njt_http_v3_upstream_connected(njt_http_request_t *r, njt_http_upstream_t *u, + njt_connection_t *sc) +{ + njt_int_t rc; + njt_connection_t *c; + + njt_log_debug0(NJT_LOG_DEBUG_HTTP, sc->log, 0, "http3 upstream connected"); + + if (sc->write->timer_set) { + /* remove connection timeout timer */ + njt_del_timer(sc->write); + } + + sc->write->handler = njt_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) { + njt_log_error(NJT_LOG_ERR, c->log, 0, + "upstream SSL certificate verify error: (%l:%s)", + rc, X509_verify_cert_error_string(rc)); + + return NJT_DECLINED; + } + + if (njt_ssl_check_host(c, &u->ssl_name) != NJT_OK) { + njt_log_error(NJT_LOG_ERR, c->log, 0, + "upstream SSL certificate does not match \"%V\"", + &u->ssl_name); + return NJT_DECLINED; + } + } + + if (!u->hq) { + if (njt_http_v3_send_settings(c) != NJT_OK) { + /* example error: qc->closing is set */ + return NJT_ERROR; + } + } + + if (njt_http_v3_upstream_send_request(r, u, sc) != NJT_OK) { + return NJT_ERROR; + } + + return NJT_OK; +} + + +static njt_int_t +njt_http_v3_upstream_send_request(njt_http_request_t *r, + njt_http_upstream_t *u, njt_connection_t *sc) +{ + njt_log_debug2(NJT_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 = njt_http_upstream_handler; + sc->read->handler = njt_http_upstream_handler; + + u->writer.connection = sc; + u->h3_started = 1; + + njt_http_upstream_send_request(r, u, 1); + + return NJT_OK; +} + + +void +njt_http_v3_upstream_close_request_stream(njt_connection_t *c, + njt_uint_t do_reset) +{ + njt_pool_t *pool; + + njt_log_debug0(NJT_LOG_DEBUG_HTTP, c->log, 0, + "http3 upstream close request stream"); + + if (do_reset) { + njt_http_v3_reset_stream(c); + } + + c->destroyed = 1; + + pool = c->pool; + + njt_quic_client_set_ssl_data(c->quic->parent, NULL); + + /* will remove any c->read/write timers */ + njt_close_connection(c); + + /* will trigger quic stream cleanup handler */ + njt_destroy_pool(pool); +} + + +static void +njt_http_quic_upstream_dummy_handler(njt_event_t *ev) +{ +} + + +static void +njt_http_quic_stream_close_handler(njt_event_t *ev) +{ + njt_connection_t *c, *sc; + njt_http_request_t *r; + njt_http_upstream_t *u; + + njt_log_debug0(NJT_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 njt_http_upstream_close_peer_connection() + */ + + njt_http_upstream_next(r, u, NJT_HTTP_UPSTREAM_FT_ERROR); + + njt_http_run_posted_requests(c); + } +} + +#endif static njt_int_t njt_http_upstream_set_local(njt_http_request_t *r, njt_http_upstream_t *u, diff --git a/src/http/njt_http_upstream.h b/src/http/njt_http_upstream.h index 73f8e139..3a417db9 100644 --- a/src/http/njt_http_upstream.h +++ b/src/http/njt_http_upstream.h @@ -3,6 +3,7 @@ * Copyright (C) Igor Sysoev * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -510,6 +511,10 @@ njt_int_t njt_http_upstream_hide_headers_hash(njt_conf_t *cf, void njt_http_upstream_connect(njt_http_request_t *r, njt_http_upstream_t *u); //end add by clb +#if (NJT_HTTP_V3) +void njt_http_v3_upstream_close_request_stream(njt_connection_t *c, + njt_uint_t do_reset); +#endif #define njt_http_conf_upstream_srv_conf(uscf, module) \ uscf->srv_conf[module.ctx_index] diff --git a/src/http/v2/njt_http_v2.c b/src/http/v2/njt_http_v2.c index 9a2f671c..dc30f3f9 100755 --- a/src/http/v2/njt_http_v2.c +++ b/src/http/v2/njt_http_v2.c @@ -3632,7 +3632,7 @@ njt_http_v2_parse_authority(njt_http_request_t *r, njt_str_t *value) if (hh->handler(r, h, hh->offset) != NJT_OK) { /* * request has been finalized already - * in ngx_http_process_host() + * in njt_http_process_host() */ return NJT_ABORT; } diff --git a/src/http/v2/njt_http_v2_stream.c b/src/http/v2/njt_http_v2_stream.c index d78bf0b7..58220dfd 100644 --- a/src/http/v2/njt_http_v2_stream.c +++ b/src/http/v2/njt_http_v2_stream.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 Kern + * Copyright (C) 2024 JD Technology Information Technology Co., Ltd */ #include diff --git a/src/http/v3/njt_http_v3.h b/src/http/v3/njt_http_v3.h index 15dbd0b1..0493a817 100644 --- a/src/http/v3/njt_http_v3.h +++ b/src/http/v3/njt_http_v3.h @@ -3,6 +3,7 @@ * Copyright (C) Roman Arutyunyan * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -73,6 +74,29 @@ #define NJT_HTTP_V3_ERR_CONNECT_ERROR 0x10f #define NJT_HTTP_V3_ERR_VERSION_FALLBACK 0x110 +/* static table indices */ +#define NJT_HTTP_V3_HEADER_AUTHORITY 0 +#define NJT_HTTP_V3_HEADER_PATH_ROOT 1 +#define NJT_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4 +#define NJT_HTTP_V3_HEADER_DATE 6 +#define NJT_HTTP_V3_HEADER_LAST_MODIFIED 10 +#define NJT_HTTP_V3_HEADER_LOCATION 12 +#define NJT_HTTP_V3_HEADER_METHOD_DELETE 16 +#define NJT_HTTP_V3_HEADER_METHOD_GET 17 +#define NJT_HTTP_V3_HEADER_METHOD_HEAD 18 +#define NJT_HTTP_V3_HEADER_METHOD_OPTIONS 19 +#define NJT_HTTP_V3_HEADER_METHOD_POST 20 +#define NJT_HTTP_V3_HEADER_METHOD_PUT 21 +#define NJT_HTTP_V3_HEADER_SCHEME_HTTP 22 +#define NJT_HTTP_V3_HEADER_SCHEME_HTTPS 23 +#define NJT_HTTP_V3_HEADER_STATUS_200 25 +#define NJT_HTTP_V3_HEADER_ACCEPT_ENCODING 31 +#define NJT_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 +#define NJT_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 +#define NJT_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72 +#define NJT_HTTP_V3_HEADER_SERVER 92 +#define NJT_HTTP_V3_HEADER_USER_AGENT 95 + /* QPACK errors */ #define NJT_HTTP_V3_ERR_DECOMPRESSION_FAILED 0x200 #define NJT_HTTP_V3_ERR_ENCODER_STREAM_ERROR 0x201 @@ -139,11 +163,13 @@ struct njt_http_v3_session_s { unsigned goaway:1; unsigned hq:1; + unsigned client:1; njt_connection_t *known_streams[NJT_HTTP_V3_MAX_KNOWN_STREAM]; }; +void njt_http_v3_init_client_stream(njt_connection_t *c); void njt_http_v3_init_stream(njt_connection_t *c); void njt_http_v3_reset_stream(njt_connection_t *c); njt_int_t njt_http_v3_init_session(njt_connection_t *c); diff --git a/src/http/v3/njt_http_v3_module.c b/src/http/v3/njt_http_v3_module.c index f4afad13..7398c265 100644 --- a/src/http/v3/njt_http_v3_module.c +++ b/src/http/v3/njt_http_v3_module.c @@ -3,6 +3,7 @@ * Copyright (C) Nginx, Inc. * Copyright (C) Roman Arutyunyan * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -13,6 +14,8 @@ static njt_int_t njt_http_v3_variable(njt_http_request_t *r, njt_http_variable_value_t *v, uintptr_t data); +static njt_int_t njt_http_v3_quic_connection_variable(njt_http_request_t *r, + njt_http_variable_value_t *v, uintptr_t data); static njt_int_t njt_http_v3_add_variables(njt_conf_t *cf); static void *njt_http_v3_create_srv_conf(njt_conf_t *cf); static char *njt_http_v3_merge_srv_conf(njt_conf_t *cf, void *parent, @@ -119,6 +122,9 @@ static njt_http_variable_t njt_http_v3_vars[] = { { njt_string("http3"), NULL, njt_http_v3_variable, 0, 0, 0, 0 }, + { njt_string("quic_connection"), NULL, njt_http_v3_quic_connection_variable, + 0, 0, 0, 0 }, + njt_http_null_variable }; @@ -158,6 +164,38 @@ njt_http_v3_variable(njt_http_request_t *r, return NJT_OK; } +static njt_int_t +njt_http_v3_quic_connection_variable(njt_http_request_t *r, + njt_http_variable_value_t *v, uintptr_t data) +{ + u_char *p; + njt_connection_t *c; + njt_quic_stream_t *qs; + + if (r->connection->quic) { + qs = r->connection->quic; + + c = qs->parent; + + p = njt_pnalloc(r->pool, NJT_ATOMIC_T_LEN); + if (p == NULL) { + return NJT_ERROR; + } + + v->len = njt_sprintf(p, "%uA", c->number) - p; + v->valid = 1; + v->no_cacheable = 0; + v->not_found = 0; + v->data = p; + + return NJT_OK; + } + + *v = njt_http_variable_null_value; + + return NJT_OK; + +} static njt_int_t njt_http_v3_add_variables(njt_conf_t *cf) diff --git a/src/http/v3/njt_http_v3_parse.c b/src/http/v3/njt_http_v3_parse.c index 34750c46..b4fceaa9 100644 --- a/src/http/v3/njt_http_v3_parse.c +++ b/src/http/v3/njt_http_v3_parse.c @@ -3,6 +3,7 @@ * Copyright (C) Roman Arutyunyan * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -354,10 +355,12 @@ njt_http_v3_parse_headers(njt_connection_t *c, njt_http_v3_parse_headers_t *st, case sw_verify: - rc = njt_http_v3_check_insert_count(c, st->prefix.insert_count); - if (rc != NJT_OK) { - return rc; - } + if (c->quic != NULL) { + rc = njt_http_v3_check_insert_count(c, st->prefix.insert_count); + if (rc != NJT_OK) { + return rc; + } + } /* else: check skipped for cached response */ st->state = sw_field_rep; @@ -393,9 +396,11 @@ done: njt_log_debug0(NJT_LOG_DEBUG_HTTP, c->log, 0, "http3 parse headers done"); if (st->prefix.insert_count > 0) { - if (njt_http_v3_send_ack_section(c, c->quic->id) != NJT_OK) { - return NJT_ERROR; - } + if (c->quic) { + if (njt_http_v3_send_ack_section(c, c->quic->id) != NJT_OK) { + return NJT_ERROR; + } + } /* skip for cached response */ njt_http_v3_ack_insert_count(c, st->prefix.insert_count); } @@ -615,13 +620,15 @@ njt_http_v3_parse_literal(njt_connection_t *c, njt_http_v3_parse_literal_t *st, n = st->length; - cscf = njt_http_v3_get_module_srv_conf(c, njt_http_core_module); + if (c->quic != NULL) { + cscf = njt_http_v3_get_module_srv_conf(c, njt_http_core_module); - if (n > cscf->large_client_header_buffers.size) { - njt_log_error(NJT_LOG_INFO, c->log, 0, - "client sent too large field line"); - return NJT_HTTP_V3_ERR_EXCESSIVE_LOAD; - } + if (n > cscf->large_client_header_buffers.size) { + njt_log_error(NJT_LOG_INFO, c->log, 0, + "client sent too large field line"); + return NJT_HTTP_V3_ERR_EXCESSIVE_LOAD; + } + } /* else: check skipped for cached response */ if (st->huffman) { n = n * 8 / 5; diff --git a/src/http/v3/njt_http_v3_request.c b/src/http/v3/njt_http_v3_request.c index 6fc0b224..97e17ecc 100644 --- a/src/http/v3/njt_http_v3_request.c +++ b/src/http/v3/njt_http_v3_request.c @@ -3,6 +3,7 @@ * Copyright (C) Roman Arutyunyan * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -55,6 +56,26 @@ static const struct { { njt_string("CONNECT"), NJT_HTTP_CONNECT } }; +void +njt_http_v3_init_client_stream(njt_connection_t *c) +{ + njt_http_connection_t *hc, *phc; + + phc = njt_http_quic_get_connection(c); + + hc = njt_pcalloc(c->pool, sizeof(njt_http_connection_t)); + if (hc == NULL) { + njt_http_close_connection(c); + return; + } + + c->data = hc; + + /* server configuration used by 'client' streams */ + hc->conf_ctx = phc->conf_ctx; + + njt_http_v3_init_stream(c); +} void njt_http_v3_init_stream(njt_connection_t *c) diff --git a/src/http/v3/njt_http_v3_uni.c b/src/http/v3/njt_http_v3_uni.c index 761d68c6..cabc10f0 100644 --- a/src/http/v3/njt_http_v3_uni.c +++ b/src/http/v3/njt_http_v3_uni.c @@ -3,6 +3,7 @@ * Copyright (C) Roman Arutyunyan * Copyright (C) Nginx, Inc. * Copyright (C) 2021-2023 TMLake(Beijing) Technology Co., Ltd. + * Copyright (C) 2023 Web Server LLC */ @@ -110,32 +111,44 @@ njt_int_t njt_http_v3_register_uni_stream(njt_connection_t *c, uint64_t type) { njt_int_t index; + njt_uint_t encoder, decoder, control; njt_http_v3_session_t *h3c; njt_http_v3_uni_stream_t *us; h3c = njt_http_v3_get_session(c); + if (h3c->client) { + encoder = NJT_HTTP_V3_STREAM_SERVER_ENCODER; + decoder = NJT_HTTP_V3_STREAM_SERVER_DECODER; + control = NJT_HTTP_V3_STREAM_SERVER_CONTROL; + + } else { + encoder = NJT_HTTP_V3_STREAM_CLIENT_ENCODER; + decoder = NJT_HTTP_V3_STREAM_CLIENT_DECODER; + control = NJT_HTTP_V3_STREAM_CLIENT_CONTROL; + } + switch (type) { case NJT_HTTP_V3_STREAM_ENCODER: njt_log_debug0(NJT_LOG_DEBUG_HTTP, c->log, 0, "http3 encoder stream"); - index = NJT_HTTP_V3_STREAM_CLIENT_ENCODER; + index = encoder; break; case NJT_HTTP_V3_STREAM_DECODER: njt_log_debug0(NJT_LOG_DEBUG_HTTP, c->log, 0, "http3 decoder stream"); - index = NJT_HTTP_V3_STREAM_CLIENT_DECODER; + index = decoder; break; case NJT_HTTP_V3_STREAM_CONTROL: njt_log_debug0(NJT_LOG_DEBUG_HTTP, c->log, 0, "http3 control stream"); - index = NJT_HTTP_V3_STREAM_CLIENT_CONTROL; + index = control; break; @@ -144,9 +157,9 @@ njt_http_v3_register_uni_stream(njt_connection_t *c, uint64_t type) njt_log_debug1(NJT_LOG_DEBUG_HTTP, c->log, 0, "http3 stream 0x%02xL", type); - if (h3c->known_streams[NJT_HTTP_V3_STREAM_CLIENT_ENCODER] == NULL - || h3c->known_streams[NJT_HTTP_V3_STREAM_CLIENT_DECODER] == NULL - || h3c->known_streams[NJT_HTTP_V3_STREAM_CLIENT_CONTROL] == NULL) + if (h3c->known_streams[encoder] == NULL + || h3c->known_streams[decoder] == NULL + || h3c->known_streams[control] == NULL) { njt_log_error(NJT_LOG_INFO, c->log, 0, "missing mandatory stream"); return NJT_HTTP_V3_ERR_STREAM_CREATION_ERROR; @@ -320,21 +333,25 @@ njt_http_v3_get_uni_stream(njt_connection_t *c, njt_uint_t type) njt_http_v3_session_t *h3c; njt_http_v3_uni_stream_t *us; + h3c = njt_http_v3_get_session(c); + switch (type) { case NJT_HTTP_V3_STREAM_ENCODER: - index = NJT_HTTP_V3_STREAM_SERVER_ENCODER; + index = h3c->client ? NJT_HTTP_V3_STREAM_CLIENT_ENCODER + : NJT_HTTP_V3_STREAM_SERVER_ENCODER; break; case NJT_HTTP_V3_STREAM_DECODER: - index = NJT_HTTP_V3_STREAM_SERVER_DECODER; + index = h3c->client ? NJT_HTTP_V3_STREAM_CLIENT_DECODER + : NJT_HTTP_V3_STREAM_SERVER_DECODER; break; case NJT_HTTP_V3_STREAM_CONTROL: - index = NJT_HTTP_V3_STREAM_SERVER_CONTROL; + index = h3c->client ? NJT_HTTP_V3_STREAM_CLIENT_CONTROL + : NJT_HTTP_V3_STREAM_SERVER_CONTROL; break; default: index = -1; } - h3c = njt_http_v3_get_session(c); if (index >= 0) { if (h3c->known_streams[index]) { @@ -383,10 +400,13 @@ njt_http_v3_get_uni_stream(njt_connection_t *c, njt_uint_t type) failed: - njt_log_error(NJT_LOG_ERR, c->log, 0, "failed to create server stream"); + njt_log_error(NJT_LOG_ERR, c->log, 0, + h3c->client ? "failed to create client stream" + : "failed to create server stream"); njt_http_v3_finalize_connection(c, NJT_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) { njt_http_v3_close_uni_stream(sc); } -- Gitee From d928915199bf3ef0258e98f8d8177eeca8e11230 Mon Sep 17 00:00:00 2001 From: kern Date: Wed, 31 Jul 2024 20:33:56 +0800 Subject: [PATCH 3/4] opt http2 RST_STREAM --- src/http/modules/njt_http_proxy_module.c | 5 +---- src/http/njt_http_request_body.c | 7 +++---- src/http/njt_http_upstream.c | 3 +-- src/http/v2/njt_http_v2.c | 21 ++++++++++++++++++--- src/http/v2/njt_http_v2_filter_module.c | 2 +- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/src/http/modules/njt_http_proxy_module.c b/src/http/modules/njt_http_proxy_module.c index 3a6ed54d..e974dce6 100644 --- a/src/http/modules/njt_http_proxy_module.c +++ b/src/http/modules/njt_http_proxy_module.c @@ -6010,10 +6010,7 @@ njt_http_v2_proxy_create_request(njt_http_request_t *r) b->last_buf = r->stream->in_closed || (r->headers_in.content_length_n <= 0 && !r->headers_in.chunked); } else { - //当下游为http3时,r->headers_in.content_length_n == 0和 - //r->headers_in.chunked ==1 同时成立 - b->last_buf = r->headers_in.content_length_n == 0 || - (r->headers_in.content_length_n < 0 && !r->headers_in.chunked); + b->last_buf = r->headers_in.content_length_n <= 0 && !r->headers_in.chunked; } cl->buf = b; diff --git a/src/http/njt_http_request_body.c b/src/http/njt_http_request_body.c index 7a02639c..c5edd978 100644 --- a/src/http/njt_http_request_body.c +++ b/src/http/njt_http_request_body.c @@ -641,11 +641,10 @@ njt_http_discard_request_body(njt_http_request_t *r) #if (NJT_HTTP_V2) if (r->stream) { r->stream->skip_data = 1; - //forbid RST_STEAM - r->stream->in_closed = 1; - /*这种情况下,接收包头,未接收完包体,即向后端转发请求, + /*在接收包头,未接收完包体,即向后端转发请求, 后端响应请求后,即关闭stream,导致RST_STEAM发送, - 进而导致request失败,需优化HTTP2废弃包处理*/ + 在上游处理区分处理了该状况,认为正常*/ + //r->stream->in_closed = 1; return NJT_OK; } #endif diff --git a/src/http/njt_http_upstream.c b/src/http/njt_http_upstream.c index 31350746..3b5ef753 100644 --- a/src/http/njt_http_upstream.c +++ b/src/http/njt_http_upstream.c @@ -7947,8 +7947,7 @@ njt_http_v2_upstream_send_header(njt_http_request_t *r) { return NJT_OK; } - fin = stream->in_closed || r->headers_in.content_length_n == 0 || - (r->headers_in.content_length_n < 0 && !r->headers_in.chunked); + fin = stream->in_closed || (r->headers_in.content_length_n <= 0 && !r->headers_in.chunked); njt_buf_t *out = u->request_bufs->buf; frame = njt_http_v2_create_headers_frame(stream,out->pos,out->last,fin); diff --git a/src/http/v2/njt_http_v2.c b/src/http/v2/njt_http_v2.c index dc30f3f9..3d9af5e8 100755 --- a/src/http/v2/njt_http_v2.c +++ b/src/http/v2/njt_http_v2.c @@ -2166,22 +2166,37 @@ njt_http_v2_state_rst_stream(njt_http_v2_connection_t *h2c, u_char *pos, stream->out_closed = 1; fc = stream->fc; - fc->error = 1; + //fc->error = 1; switch (status) { case NJT_HTTP_V2_CANCEL: + fc->error = 1; njt_log_error(NJT_LOG_INFO, fc->log, 0, "client canceled stream %ui", h2c->state.sid); break; case NJT_HTTP_V2_INTERNAL_ERROR: + fc->error = 1; njt_log_error(NJT_LOG_INFO, fc->log, 0, "client terminated stream %ui due to internal error", h2c->state.sid); break; + case NJT_HTTP_V2_NO_ERROR: //out流未关闭情况下,不设置error + njt_log_error(NJT_LOG_INFO, fc->log, 0, + "client terminated stream %ui with no error", h2c->state.sid); + break; + + case NJT_HTTP_V2_PROTOCOL_ERROR: + fc->error = 1; + njt_log_error(NJT_LOG_INFO, fc->log, 0, + "client terminated stream %ui with protocal error", + h2c->state.sid); + break; + default: + fc->error = 1; njt_log_error(NJT_LOG_INFO, fc->log, 0, "client terminated stream %ui with status %ui", h2c->state.sid, status); @@ -4510,8 +4525,8 @@ njt_http_v2_close_stream(njt_http_v2_stream_t *stream, njt_int_t rc) { h2c->connection->error = 1; } - - } else if (!stream->in_closed) { + //向上游发送完数据后,可能还未收到回复,此时不要RST_STREAM + } else if (!stream->in_closed && !h2c->client) { if (njt_http_v2_send_rst_stream(h2c, node->id, NJT_HTTP_V2_NO_ERROR) != NJT_OK) { diff --git a/src/http/v2/njt_http_v2_filter_module.c b/src/http/v2/njt_http_v2_filter_module.c index 31b8c58a..77521af8 100755 --- a/src/http/v2/njt_http_v2_filter_module.c +++ b/src/http/v2/njt_http_v2_filter_module.c @@ -888,7 +888,7 @@ njt_http_v2_send_chain(njt_connection_t *fc, njt_chain_t *in, off_t limit) if (size) { njt_log_error(NJT_LOG_ERR, fc->log, 0, - "output on closed stream"); + "http2 stream:%p output on closed", stream); return NJT_CHAIN_ERROR; } -- Gitee From a5441d20d805f394cbfb455849dcabeb59c92699 Mon Sep 17 00:00:00 2001 From: kern Date: Wed, 31 Jul 2024 20:34:39 +0800 Subject: [PATCH 4/4] opt http3 chunked --- src/http/v3/njt_http_v3_request.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/http/v3/njt_http_v3_request.c b/src/http/v3/njt_http_v3_request.c index 97e17ecc..74222a35 100644 --- a/src/http/v3/njt_http_v3_request.c +++ b/src/http/v3/njt_http_v3_request.c @@ -1091,6 +1091,7 @@ njt_http_v3_process_request_header(njt_http_request_t *r) } } + //NJT_AGAIN 这种情况下,不确定是否应设置chunked if (n != 0) { r->headers_in.chunked = 1; } @@ -1690,6 +1691,10 @@ done: if (r->headers_in.content_length_n == -1) { r->headers_in.content_length_n = rb->received; + //如果没有包体,清除chunked标记 + if (rb->received == 0) { + r->headers_in.chunked = 0; + } } else if (r->headers_in.content_length_n != rb->received) { njt_log_error(NJT_LOG_INFO, r->connection->log, 0, -- Gitee