Mercurial > hg > nginx
view src/http/v2/ngx_http_v2_table.c @ 7769:0a5687a458de
HTTP/2: fixed reusing connections with active requests.
New connections are marked reusable by ngx_http_init_connection() if there
are no data available for reading. As a result, if SSL is not used,
ngx_http_v2_init() might be called when the connection is marked reusable.
If a HEADERS frame is immediately available for reading, this resulted
in connection being preserved in reusable state with an active request,
and possibly closed later as if during worker shutdown (that is, after
all active requests were finalized).
Fix is to explicitly mark connections non-reusable in ngx_http_v2_init()
instead of (incorrectly) assuming they are already non-reusable.
Found by Sergey Kandaurov.
author | Maxim Dounin <mdounin@mdounin.ru> |
---|---|
date | Thu, 11 Feb 2021 21:52:17 +0300 |
parents | 87e9e4aabf1b |
children |
line wrap: on
line source
/* * Copyright (C) Nginx, Inc. * Copyright (C) Valentin V. Bartenev */ #include <ngx_config.h> #include <ngx_core.h> #include <ngx_http.h> #define NGX_HTTP_V2_TABLE_SIZE 4096 static ngx_int_t ngx_http_v2_table_account(ngx_http_v2_connection_t *h2c, size_t size); static ngx_http_v2_header_t ngx_http_v2_static_table[] = { { ngx_string(":authority"), ngx_string("") }, { ngx_string(":method"), ngx_string("GET") }, { ngx_string(":method"), ngx_string("POST") }, { ngx_string(":path"), ngx_string("/") }, { ngx_string(":path"), ngx_string("/index.html") }, { ngx_string(":scheme"), ngx_string("http") }, { ngx_string(":scheme"), ngx_string("https") }, { ngx_string(":status"), ngx_string("200") }, { ngx_string(":status"), ngx_string("204") }, { ngx_string(":status"), ngx_string("206") }, { ngx_string(":status"), ngx_string("304") }, { ngx_string(":status"), ngx_string("400") }, { ngx_string(":status"), ngx_string("404") }, { ngx_string(":status"), ngx_string("500") }, { ngx_string("accept-charset"), ngx_string("") }, { ngx_string("accept-encoding"), ngx_string("gzip, deflate") }, { ngx_string("accept-language"), ngx_string("") }, { ngx_string("accept-ranges"), ngx_string("") }, { ngx_string("accept"), ngx_string("") }, { ngx_string("access-control-allow-origin"), ngx_string("") }, { ngx_string("age"), ngx_string("") }, { ngx_string("allow"), ngx_string("") }, { ngx_string("authorization"), ngx_string("") }, { ngx_string("cache-control"), ngx_string("") }, { ngx_string("content-disposition"), ngx_string("") }, { ngx_string("content-encoding"), ngx_string("") }, { ngx_string("content-language"), ngx_string("") }, { ngx_string("content-length"), ngx_string("") }, { ngx_string("content-location"), ngx_string("") }, { ngx_string("content-range"), ngx_string("") }, { ngx_string("content-type"), ngx_string("") }, { ngx_string("cookie"), ngx_string("") }, { ngx_string("date"), ngx_string("") }, { ngx_string("etag"), ngx_string("") }, { ngx_string("expect"), ngx_string("") }, { ngx_string("expires"), ngx_string("") }, { ngx_string("from"), ngx_string("") }, { ngx_string("host"), ngx_string("") }, { ngx_string("if-match"), ngx_string("") }, { ngx_string("if-modified-since"), ngx_string("") }, { ngx_string("if-none-match"), ngx_string("") }, { ngx_string("if-range"), ngx_string("") }, { ngx_string("if-unmodified-since"), ngx_string("") }, { ngx_string("last-modified"), ngx_string("") }, { ngx_string("link"), ngx_string("") }, { ngx_string("location"), ngx_string("") }, { ngx_string("max-forwards"), ngx_string("") }, { ngx_string("proxy-authenticate"), ngx_string("") }, { ngx_string("proxy-authorization"), ngx_string("") }, { ngx_string("range"), ngx_string("") }, { ngx_string("referer"), ngx_string("") }, { ngx_string("refresh"), ngx_string("") }, { ngx_string("retry-after"), ngx_string("") }, { ngx_string("server"), ngx_string("") }, { ngx_string("set-cookie"), ngx_string("") }, { ngx_string("strict-transport-security"), ngx_string("") }, { ngx_string("transfer-encoding"), ngx_string("") }, { ngx_string("user-agent"), ngx_string("") }, { ngx_string("vary"), ngx_string("") }, { ngx_string("via"), ngx_string("") }, { ngx_string("www-authenticate"), ngx_string("") }, }; #define NGX_HTTP_V2_STATIC_TABLE_ENTRIES \ (sizeof(ngx_http_v2_static_table) \ / sizeof(ngx_http_v2_header_t)) ngx_str_t * ngx_http_v2_get_static_name(ngx_uint_t index) { return &ngx_http_v2_static_table[index - 1].name; } ngx_str_t * ngx_http_v2_get_static_value(ngx_uint_t index) { return &ngx_http_v2_static_table[index - 1].value; } ngx_int_t ngx_http_v2_get_indexed_header(ngx_http_v2_connection_t *h2c, ngx_uint_t index, ngx_uint_t name_only) { u_char *p; size_t rest; ngx_http_v2_header_t *entry; if (index == 0) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent invalid hpack table index 0"); return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 get indexed %s: %ui", name_only ? "name" : "header", index); index--; if (index < NGX_HTTP_V2_STATIC_TABLE_ENTRIES) { h2c->state.header = ngx_http_v2_static_table[index]; return NGX_OK; } index -= NGX_HTTP_V2_STATIC_TABLE_ENTRIES; if (index < h2c->hpack.added - h2c->hpack.deleted) { index = (h2c->hpack.added - index - 1) % h2c->hpack.allocated; entry = h2c->hpack.entries[index]; p = ngx_pnalloc(h2c->state.pool, entry->name.len + 1); if (p == NULL) { return NGX_ERROR; } h2c->state.header.name.len = entry->name.len; h2c->state.header.name.data = p; rest = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - entry->name.data; if (entry->name.len > rest) { p = ngx_cpymem(p, entry->name.data, rest); p = ngx_cpymem(p, h2c->hpack.storage, entry->name.len - rest); } else { p = ngx_cpymem(p, entry->name.data, entry->name.len); } *p = '\0'; if (name_only) { return NGX_OK; } p = ngx_pnalloc(h2c->state.pool, entry->value.len + 1); if (p == NULL) { return NGX_ERROR; } h2c->state.header.value.len = entry->value.len; h2c->state.header.value.data = p; rest = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - entry->value.data; if (entry->value.len > rest) { p = ngx_cpymem(p, entry->value.data, rest); p = ngx_cpymem(p, h2c->hpack.storage, entry->value.len - rest); } else { p = ngx_cpymem(p, entry->value.data, entry->value.len); } *p = '\0'; return NGX_OK; } ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent out of bound hpack table index: %ui", index); return NGX_ERROR; } ngx_int_t ngx_http_v2_add_header(ngx_http_v2_connection_t *h2c, ngx_http_v2_header_t *header) { size_t avail; ngx_uint_t index; ngx_http_v2_header_t *entry, **entries; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 table add: \"%V: %V\"", &header->name, &header->value); if (h2c->hpack.entries == NULL) { h2c->hpack.allocated = 64; h2c->hpack.size = NGX_HTTP_V2_TABLE_SIZE; h2c->hpack.free = NGX_HTTP_V2_TABLE_SIZE; h2c->hpack.entries = ngx_palloc(h2c->connection->pool, sizeof(ngx_http_v2_header_t *) * h2c->hpack.allocated); if (h2c->hpack.entries == NULL) { return NGX_ERROR; } h2c->hpack.storage = ngx_palloc(h2c->connection->pool, h2c->hpack.free); if (h2c->hpack.storage == NULL) { return NGX_ERROR; } h2c->hpack.pos = h2c->hpack.storage; } if (ngx_http_v2_table_account(h2c, header->name.len + header->value.len) != NGX_OK) { return NGX_OK; } if (h2c->hpack.reused == h2c->hpack.deleted) { entry = ngx_palloc(h2c->connection->pool, sizeof(ngx_http_v2_header_t)); if (entry == NULL) { return NGX_ERROR; } } else { entry = h2c->hpack.entries[h2c->hpack.reused++ % h2c->hpack.allocated]; } avail = h2c->hpack.storage + NGX_HTTP_V2_TABLE_SIZE - h2c->hpack.pos; entry->name.len = header->name.len; entry->name.data = h2c->hpack.pos; if (avail >= header->name.len) { h2c->hpack.pos = ngx_cpymem(h2c->hpack.pos, header->name.data, header->name.len); } else { ngx_memcpy(h2c->hpack.pos, header->name.data, avail); h2c->hpack.pos = ngx_cpymem(h2c->hpack.storage, header->name.data + avail, header->name.len - avail); avail = NGX_HTTP_V2_TABLE_SIZE; } avail -= header->name.len; entry->value.len = header->value.len; entry->value.data = h2c->hpack.pos; if (avail >= header->value.len) { h2c->hpack.pos = ngx_cpymem(h2c->hpack.pos, header->value.data, header->value.len); } else { ngx_memcpy(h2c->hpack.pos, header->value.data, avail); h2c->hpack.pos = ngx_cpymem(h2c->hpack.storage, header->value.data + avail, header->value.len - avail); } if (h2c->hpack.allocated == h2c->hpack.added - h2c->hpack.deleted) { entries = ngx_palloc(h2c->connection->pool, sizeof(ngx_http_v2_header_t *) * (h2c->hpack.allocated + 64)); if (entries == NULL) { return NGX_ERROR; } index = h2c->hpack.deleted % h2c->hpack.allocated; ngx_memcpy(entries, &h2c->hpack.entries[index], (h2c->hpack.allocated - index) * sizeof(ngx_http_v2_header_t *)); ngx_memcpy(&entries[h2c->hpack.allocated - index], h2c->hpack.entries, index * sizeof(ngx_http_v2_header_t *)); (void) ngx_pfree(h2c->connection->pool, h2c->hpack.entries); h2c->hpack.entries = entries; h2c->hpack.added = h2c->hpack.allocated; h2c->hpack.deleted = 0; h2c->hpack.reused = 0; h2c->hpack.allocated += 64; } h2c->hpack.entries[h2c->hpack.added++ % h2c->hpack.allocated] = entry; return NGX_OK; } static ngx_int_t ngx_http_v2_table_account(ngx_http_v2_connection_t *h2c, size_t size) { ngx_http_v2_header_t *entry; size += 32; ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 table account: %uz free:%uz", size, h2c->hpack.free); if (size <= h2c->hpack.free) { h2c->hpack.free -= size; return NGX_OK; } if (size > h2c->hpack.size) { h2c->hpack.deleted = h2c->hpack.added; h2c->hpack.free = h2c->hpack.size; return NGX_DECLINED; } do { entry = h2c->hpack.entries[h2c->hpack.deleted++ % h2c->hpack.allocated]; h2c->hpack.free += 32 + entry->name.len + entry->value.len; } while (size > h2c->hpack.free); h2c->hpack.free -= size; return NGX_OK; } ngx_int_t ngx_http_v2_table_size(ngx_http_v2_connection_t *h2c, size_t size) { ssize_t needed; ngx_http_v2_header_t *entry; if (size > NGX_HTTP_V2_TABLE_SIZE) { ngx_log_error(NGX_LOG_INFO, h2c->connection->log, 0, "client sent invalid table size update: %uz", size); return NGX_ERROR; } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, h2c->connection->log, 0, "http2 new hpack table size: %uz was:%uz", size, h2c->hpack.size); needed = h2c->hpack.size - size; while (needed > (ssize_t) h2c->hpack.free) { entry = h2c->hpack.entries[h2c->hpack.deleted++ % h2c->hpack.allocated]; h2c->hpack.free += 32 + entry->name.len + entry->value.len; } h2c->hpack.size = size; h2c->hpack.free -= needed; return NGX_OK; }