Mercurial > hg > nginx
view src/http/v3/ngx_http_v3_filter_module.c @ 9153:8f7e6d8c061e
QUIC: use last client dcid to receive initial packets.
Previously, original dcid was used to receive initial client packets in case
server initial response was lost. However, last dcid should be used instead.
These two are the same unless retry is used. In case of retry, client resends
initial packet with a new dcid, that is different from the original dcid. If
server response is lost, the client resends this packet again with the same
dcid. This is shown in RFC 9000, 7.3. Authenticating Connection IDs, Figure 8.
The issue manifested itself with creating multiple server sessions in response
to each post-retry client initial packet, if server response is lost.
author | Roman Arutyunyan <arut@nginx.com> |
---|---|
date | Wed, 30 Aug 2023 11:09:21 +0400 |
parents | f91dc350be9f |
children | 23f109f0facc |
line wrap: on
line source
/* * Copyright (C) Roman Arutyunyan * Copyright (C) Nginx, Inc. */ #include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> /* static table indices */ #define NGX_HTTP_V3_HEADER_AUTHORITY 0 #define NGX_HTTP_V3_HEADER_PATH_ROOT 1 #define NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO 4 #define NGX_HTTP_V3_HEADER_DATE 6 #define NGX_HTTP_V3_HEADER_LAST_MODIFIED 10 #define NGX_HTTP_V3_HEADER_LOCATION 12 #define NGX_HTTP_V3_HEADER_METHOD_GET 17 #define NGX_HTTP_V3_HEADER_SCHEME_HTTP 22 #define NGX_HTTP_V3_HEADER_SCHEME_HTTPS 23 #define NGX_HTTP_V3_HEADER_STATUS_200 25 #define NGX_HTTP_V3_HEADER_ACCEPT_ENCODING 31 #define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 #define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 #define NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE 72 #define NGX_HTTP_V3_HEADER_SERVER 92 #define NGX_HTTP_V3_HEADER_USER_AGENT 95 typedef struct { ngx_chain_t *free; ngx_chain_t *busy; } ngx_http_v3_filter_ctx_t; static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r); static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in); static ngx_chain_t *ngx_http_v3_create_trailers(ngx_http_request_t *r, ngx_http_v3_filter_ctx_t *ctx); static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf); static ngx_http_module_t ngx_http_v3_filter_module_ctx = { NULL, /* preconfiguration */ ngx_http_v3_filter_init, /* postconfiguration */ NULL, /* create main configuration */ NULL, /* init main configuration */ NULL, /* create server configuration */ NULL, /* merge server configuration */ NULL, /* create location configuration */ NULL /* merge location configuration */ }; ngx_module_t ngx_http_v3_filter_module = { NGX_MODULE_V1, &ngx_http_v3_filter_module_ctx, /* module context */ NULL, /* module directives */ NGX_HTTP_MODULE, /* module type */ NULL, /* init master */ NULL, /* init module */ NULL, /* init process */ NULL, /* init thread */ NULL, /* exit thread */ NULL, /* exit process */ NULL, /* exit master */ NGX_MODULE_V1_PADDING }; static ngx_http_output_header_filter_pt ngx_http_next_header_filter; static ngx_http_output_body_filter_pt ngx_http_next_body_filter; static ngx_int_t ngx_http_v3_header_filter(ngx_http_request_t *r) { u_char *p; size_t len, n; ngx_buf_t *b; ngx_str_t host, location; ngx_uint_t i, port; ngx_chain_t *out, *hl, *cl, **ll; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *c; ngx_http_v3_session_t *h3c; ngx_http_v3_filter_ctx_t *ctx; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; u_char addr[NGX_SOCKADDR_STRLEN]; if (r->http_version != NGX_HTTP_VERSION_30) { return ngx_http_next_header_filter(r); } if (r->header_sent) { return NGX_OK; } r->header_sent = 1; if (r != r->main) { return NGX_OK; } h3c = ngx_http_v3_get_session(r->connection); if (r->method == NGX_HTTP_HEAD) { r->header_only = 1; } if (r->headers_out.last_modified_time != -1) { if (r->headers_out.status != NGX_HTTP_OK && r->headers_out.status != NGX_HTTP_PARTIAL_CONTENT && r->headers_out.status != NGX_HTTP_NOT_MODIFIED) { r->headers_out.last_modified_time = -1; r->headers_out.last_modified = NULL; } } if (r->headers_out.status == NGX_HTTP_NO_CONTENT) { r->header_only = 1; ngx_str_null(&r->headers_out.content_type); r->headers_out.last_modified_time = -1; r->headers_out.last_modified = NULL; r->headers_out.content_length = NULL; r->headers_out.content_length_n = -1; } if (r->headers_out.status == NGX_HTTP_NOT_MODIFIED) { r->header_only = 1; } c = r->connection; out = NULL; ll = &out; len = ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); if (r->headers_out.status == NGX_HTTP_OK) { len += ngx_http_v3_encode_field_ri(NULL, 0, NGX_HTTP_V3_HEADER_STATUS_200); } else { len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_STATUS_200, NULL, 3); } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); if (r->headers_out.server == NULL) { if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { n = sizeof(NGINX_VER) - 1; } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { n = sizeof(NGINX_VER_BUILD) - 1; } else { n = sizeof("nginx") - 1; } len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_SERVER, NULL, n); } if (r->headers_out.date == NULL) { len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE, NULL, ngx_cached_http_time.len); } if (r->headers_out.content_type.len) { n = r->headers_out.content_type.len; if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { n += sizeof("; charset=") - 1 + r->headers_out.charset.len; } len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, NULL, n); } if (r->headers_out.content_length == NULL) { if (r->headers_out.content_length_n > 0) { len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, NULL, NGX_OFF_T_LEN); } else if (r->headers_out.content_length_n == 0) { len += ngx_http_v3_encode_field_ri(NULL, 0, NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); } } if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); } if (r->headers_out.location && r->headers_out.location->value.len) { if (r->headers_out.location->value.data[0] == '/' && clcf->absolute_redirect) { if (clcf->server_name_in_redirect) { cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); host = cscf->server_name; } else if (r->headers_in.server.len) { host = r->headers_in.server; } else { host.len = NGX_SOCKADDR_STRLEN; host.data = addr; if (ngx_connection_local_sockaddr(c, &host, 0) != NGX_OK) { return NGX_ERROR; } } port = ngx_inet_get_port(c->local_sockaddr); location.len = sizeof("https://") - 1 + host.len + r->headers_out.location->value.len; if (clcf->port_in_redirect) { port = (port == 443) ? 0 : port; } else { port = 0; } if (port) { location.len += sizeof(":65535") - 1; } location.data = ngx_pnalloc(r->pool, location.len); if (location.data == NULL) { return NGX_ERROR; } p = ngx_cpymem(location.data, "https://", sizeof("https://") - 1); p = ngx_cpymem(p, host.data, host.len); if (port) { p = ngx_sprintf(p, ":%ui", port); } p = ngx_cpymem(p, r->headers_out.location->value.data, r->headers_out.location->value.len); /* update r->headers_out.location->value for possible logging */ r->headers_out.location->value.len = p - location.data; r->headers_out.location->value.data = location.data; ngx_str_set(&r->headers_out.location->key, "Location"); } r->headers_out.location->hash = 0; len += ngx_http_v3_encode_field_lri(NULL, 0, NGX_HTTP_V3_HEADER_LOCATION, NULL, r->headers_out.location->value.len); } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { if (clcf->gzip_vary) { len += ngx_http_v3_encode_field_ri(NULL, 0, NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); } else { r->gzip_vary = 0; } } #endif part = &r->headers_out.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (header[i].hash == 0) { continue; } len += ngx_http_v3_encode_field_l(NULL, &header[i].key, &header[i].value); } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); b = ngx_create_temp_buf(r->pool, len); if (b == NULL) { return NGX_ERROR; } b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->last, 0, 0, 0); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 output header: \":status: %03ui\"", r->headers_out.status); if (r->headers_out.status == NGX_HTTP_OK) { b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_STATUS_200); } else { b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_STATUS_200, NULL, 3); b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); } if (r->headers_out.server == NULL) { if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_ON) { p = (u_char *) NGINX_VER; n = sizeof(NGINX_VER) - 1; } else if (clcf->server_tokens == NGX_HTTP_SERVER_TOKENS_BUILD) { p = (u_char *) NGINX_VER_BUILD; n = sizeof(NGINX_VER_BUILD) - 1; } else { p = (u_char *) "nginx"; n = sizeof("nginx") - 1; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 output header: \"server: %*s\"", n, p); b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_SERVER, p, n); } if (r->headers_out.date == NULL) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 output header: \"date: %V\"", &ngx_cached_http_time); b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_DATE, ngx_cached_http_time.data, ngx_cached_http_time.len); } if (r->headers_out.content_type.len) { if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { n = r->headers_out.content_type.len + sizeof("; charset=") - 1 + r->headers_out.charset.len; p = ngx_pnalloc(r->pool, n); if (p == NULL) { return NGX_ERROR; } p = ngx_cpymem(p, r->headers_out.content_type.data, r->headers_out.content_type.len); p = ngx_cpymem(p, "; charset=", sizeof("; charset=") - 1); p = ngx_cpymem(p, r->headers_out.charset.data, r->headers_out.charset.len); /* updated r->headers_out.content_type is also needed for logging */ r->headers_out.content_type.len = n; r->headers_out.content_type.data = p - n; } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 output header: \"content-type: %V\"", &r->headers_out.content_type); b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, r->headers_out.content_type.data, r->headers_out.content_type.len); } if (r->headers_out.content_length == NULL && r->headers_out.content_length_n >= 0) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 output header: \"content-length: %O\"", r->headers_out.content_length_n); if (r->headers_out.content_length_n > 0) { p = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); n = p - b->last; b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO, NULL, n); b->last = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); } else { b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_CONTENT_LENGTH_ZERO); } } if (r->headers_out.last_modified == NULL && r->headers_out.last_modified_time != -1) { n = sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1; p = ngx_pnalloc(r->pool, n); if (p == NULL) { return NGX_ERROR; } ngx_http_time(p, r->headers_out.last_modified_time); ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 output header: \"last-modified: %*s\"", n, p); b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_LAST_MODIFIED, p, n); } if (r->headers_out.location && r->headers_out.location->value.len) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 output header: \"location: %V\"", &r->headers_out.location->value); b->last = (u_char *) ngx_http_v3_encode_field_lri(b->last, 0, NGX_HTTP_V3_HEADER_LOCATION, r->headers_out.location->value.data, r->headers_out.location->value.len); } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 output header: \"vary: Accept-Encoding\""); b->last = (u_char *) ngx_http_v3_encode_field_ri(b->last, 0, NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); } #endif part = &r->headers_out.headers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (header[i].hash == 0) { continue; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 output header: \"%V: %V\"", &header[i].key, &header[i].value); b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, &header[i].key, &header[i].value); } if (r->header_only) { b->last_buf = 1; } cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = b; cl->next = NULL; n = b->last - b->pos; h3c->payload_bytes += n; len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) + ngx_http_v3_encode_varlen_int(NULL, n); b = ngx_create_temp_buf(r->pool, len); if (b == NULL) { return NGX_ERROR; } b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, NGX_HTTP_V3_FRAME_HEADERS); b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); hl = ngx_alloc_chain_link(r->pool); if (hl == NULL) { return NGX_ERROR; } hl->buf = b; hl->next = cl; *ll = hl; ll = &cl->next; if (r->headers_out.content_length_n >= 0 && !r->header_only && !r->expect_trailers) { len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + ngx_http_v3_encode_varlen_int(NULL, r->headers_out.content_length_n); b = ngx_create_temp_buf(r->pool, len); if (b == NULL) { return NGX_ERROR; } b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, NGX_HTTP_V3_FRAME_DATA); b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, r->headers_out.content_length_n); h3c->payload_bytes += r->headers_out.content_length_n; h3c->total_bytes += r->headers_out.content_length_n; cl = ngx_alloc_chain_link(r->pool); if (cl == NULL) { return NGX_ERROR; } cl->buf = b; cl->next = NULL; *ll = cl; } else { ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_filter_ctx_t)); if (ctx == NULL) { return NGX_ERROR; } ngx_http_set_ctx(r, ctx, ngx_http_v3_filter_module); } for (cl = out; cl; cl = cl->next) { h3c->total_bytes += cl->buf->last - cl->buf->pos; r->header_size += cl->buf->last - cl->buf->pos; } return ngx_http_write_filter(r, out); } static ngx_int_t ngx_http_v3_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { u_char *chunk; off_t size; ngx_int_t rc; ngx_buf_t *b; ngx_chain_t *out, *cl, *tl, **ll; ngx_http_v3_session_t *h3c; ngx_http_v3_filter_ctx_t *ctx; if (in == NULL) { return ngx_http_next_body_filter(r, in); } ctx = ngx_http_get_module_ctx(r, ngx_http_v3_filter_module); if (ctx == NULL) { return ngx_http_next_body_filter(r, in); } h3c = ngx_http_v3_get_session(r->connection); out = NULL; ll = &out; size = 0; cl = in; for ( ;; ) { ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 chunk: %O", ngx_buf_size(cl->buf)); size += ngx_buf_size(cl->buf); if (cl->buf->flush || cl->buf->sync || ngx_buf_in_memory(cl->buf) || cl->buf->in_file) { tl = ngx_alloc_chain_link(r->pool); if (tl == NULL) { return NGX_ERROR; } tl->buf = cl->buf; *ll = tl; ll = &tl->next; } if (cl->next == NULL) { break; } cl = cl->next; } if (size) { tl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (tl == NULL) { return NGX_ERROR; } b = tl->buf; chunk = b->start; if (chunk == NULL) { chunk = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); if (chunk == NULL) { return NGX_ERROR; } b->start = chunk; b->end = chunk + NGX_HTTP_V3_VARLEN_INT_LEN * 2; } b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; b->memory = 0; b->temporary = 1; b->pos = chunk; b->last = (u_char *) ngx_http_v3_encode_varlen_int(chunk, NGX_HTTP_V3_FRAME_DATA); b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, size); tl->next = out; out = tl; h3c->payload_bytes += size; } if (cl->buf->last_buf) { tl = ngx_http_v3_create_trailers(r, ctx); if (tl == NULL) { return NGX_ERROR; } cl->buf->last_buf = 0; *ll = tl; } else { *ll = NULL; } for (cl = out; cl; cl = cl->next) { h3c->total_bytes += cl->buf->last - cl->buf->pos; } rc = ngx_http_next_body_filter(r, out); ngx_chain_update_chains(r->pool, &ctx->free, &ctx->busy, &out, (ngx_buf_tag_t) &ngx_http_v3_filter_module); return rc; } static ngx_chain_t * ngx_http_v3_create_trailers(ngx_http_request_t *r, ngx_http_v3_filter_ctx_t *ctx) { size_t len, n; u_char *p; ngx_buf_t *b; ngx_uint_t i; ngx_chain_t *cl, *hl; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_http_v3_session_t *h3c; h3c = ngx_http_v3_get_session(r->connection); len = 0; part = &r->headers_out.trailers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (header[i].hash == 0) { continue; } len += ngx_http_v3_encode_field_l(NULL, &header[i].key, &header[i].value); } cl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (cl == NULL) { return NULL; } b = cl->buf; b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; b->memory = 0; b->last_buf = 1; if (len == 0) { b->temporary = 0; b->pos = b->last = NULL; return cl; } b->temporary = 1; len += ngx_http_v3_encode_field_section_prefix(NULL, 0, 0, 0); b->pos = ngx_palloc(r->pool, len); if (b->pos == NULL) { return NULL; } b->last = (u_char *) ngx_http_v3_encode_field_section_prefix(b->pos, 0, 0, 0); part = &r->headers_out.trailers.part; header = part->elts; for (i = 0; /* void */; i++) { if (i >= part->nelts) { if (part->next == NULL) { break; } part = part->next; header = part->elts; i = 0; } if (header[i].hash == 0) { continue; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 output trailer: \"%V: %V\"", &header[i].key, &header[i].value); b->last = (u_char *) ngx_http_v3_encode_field_l(b->last, &header[i].key, &header[i].value); } n = b->last - b->pos; h3c->payload_bytes += n; hl = ngx_chain_get_free_buf(r->pool, &ctx->free); if (hl == NULL) { return NULL; } b = hl->buf; p = b->start; if (p == NULL) { p = ngx_palloc(r->pool, NGX_HTTP_V3_VARLEN_INT_LEN * 2); if (p == NULL) { return NULL; } b->start = p; b->end = p + NGX_HTTP_V3_VARLEN_INT_LEN * 2; } b->tag = (ngx_buf_tag_t) &ngx_http_v3_filter_module; b->memory = 0; b->temporary = 1; b->pos = p; b->last = (u_char *) ngx_http_v3_encode_varlen_int(p, NGX_HTTP_V3_FRAME_HEADERS); b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); hl->next = cl; return hl; } static ngx_int_t ngx_http_v3_filter_init(ngx_conf_t *cf) { ngx_http_next_header_filter = ngx_http_top_header_filter; ngx_http_top_header_filter = ngx_http_v3_header_filter; ngx_http_next_body_filter = ngx_http_top_body_filter; ngx_http_top_body_filter = ngx_http_v3_body_filter; return NGX_OK; }