From 7739b6073b11086d9a3dc4b9744418070e182c33 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 13 Mar 2020 19:36:33 +0300 Subject: HTTP/3. --- src/http/v3/ngx_http_v3_request.c | 971 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 971 insertions(+) create mode 100644 src/http/v3/ngx_http_v3_request.c (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c new file mode 100644 index 000000000..b34eed98e --- /dev/null +++ b/src/http/v3/ngx_http_v3_request.c @@ -0,0 +1,971 @@ + +/* + * Copyright (C) Roman Arutyunyan + * Copyright (C) Nginx, Inc. + */ + + +#include +#include +#include + + +#define NGX_HTTP_V3_FRAME_DATA 0x00 +#define NGX_HTTP_V3_FRAME_HEADERS 0x01 +#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03 +#define NGX_HTTP_V3_FRAME_SETTINGS 0x04 +#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05 +#define NGX_HTTP_V3_FRAME_GOAWAY 0x07 +#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d + + +static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); + + +struct { + ngx_str_t name; + ngx_uint_t method; +} ngx_http_v3_methods[] = { + + { ngx_string("GET"), NGX_HTTP_GET }, + { ngx_string("POST"), NGX_HTTP_POST }, + { ngx_string("HEAD"), NGX_HTTP_HEAD }, + { ngx_string("OPTIONS"), NGX_HTTP_OPTIONS }, + { ngx_string("PROPFIND"), NGX_HTTP_PROPFIND }, + { ngx_string("PUT"), NGX_HTTP_PUT }, + { ngx_string("MKCOL"), NGX_HTTP_MKCOL }, + { ngx_string("DELETE"), NGX_HTTP_DELETE }, + { ngx_string("COPY"), NGX_HTTP_COPY }, + { ngx_string("MOVE"), NGX_HTTP_MOVE }, + { ngx_string("PROPPATCH"), NGX_HTTP_PROPPATCH }, + { ngx_string("LOCK"), NGX_HTTP_LOCK }, + { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK }, + { ngx_string("PATCH"), NGX_HTTP_PATCH }, + { ngx_string("TRACE"), NGX_HTTP_TRACE } +}; + + +ngx_int_t +ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t pseudo) +{ + u_char *p, ch; + ngx_str_t name, value; + ngx_int_t rc; + ngx_uint_t length, index, insert_count, sign, base, delta_base, + huffman, dynamic, offset; + ngx_connection_t *c; + ngx_http_v3_header_t *h; + enum { + sw_start = 0, + sw_length, + sw_length_1, + sw_length_2, + sw_length_3, + sw_header_block, + sw_req_insert_count, + sw_delta_base, + sw_read_delta_base, + sw_header, + sw_old_header, + sw_header_ri, + sw_header_pbi, + sw_header_lri, + sw_header_lpbi, + sw_header_l_name_len, + sw_header_l_name, + sw_header_value_len, + sw_header_read_value_len, + sw_header_value + } state; + + c = r->connection; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header, pseudo:%ui", pseudo); + + if (r->state == sw_old_header) { + r->state = sw_header; + return NGX_OK; + } + + length = r->h3_length; + index = r->h3_index; + insert_count = r->h3_insert_count; + sign = r->h3_sign; + delta_base = r->h3_delta_base; + huffman = r->h3_huffman; + dynamic = r->h3_dynamic; + offset = r->h3_offset; + + name.data = r->header_name_start; + name.len = r->header_name_end - r->header_name_start; + value.data = r->header_start; + value.len = r->header_end - r->header_start; + + if (r->state == sw_start) { + length = 1; + } + +again: + + state = r->state; + + if (state == sw_header && length == 0) { + r->state = sw_start; + return NGX_HTTP_PARSE_HEADER_DONE; + } + + for (p = b->pos; p < b->last; p++) { + + if (state >= sw_header_block && length-- == 0) { + goto failed; + } + + ch = *p; + + switch (state) { + + case sw_start: + + if (ch != NGX_HTTP_V3_FRAME_HEADERS) { + goto failed; + } + + r->request_start = p; + state = sw_length; + break; + + case sw_length: + + length = ch; + if (length & 0xc0) { + state = sw_length_1; + break; + } + + state = sw_header_block; + break; + + case sw_length_1: + + length = (length << 8) + ch; + if ((length & 0xc000) != 0x4000) { + state = sw_length_2; + break; + } + + length &= 0x3fff; + state = sw_header_block; + break; + + case sw_length_2: + + length = (length << 8) + ch; + if ((length & 0xc00000) != 0x800000) { + state = sw_length_3; + break; + } + + /* fall through */ + + case sw_length_3: + + length &= 0x3fffff; + state = sw_header_block; + break; + + case sw_header_block: + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 header block length:%ui", length); + + if (ch != 0xff) { + insert_count = ch; + state = sw_delta_base; + break; + } + + insert_count = 0; + state = sw_req_insert_count; + break; + + case sw_req_insert_count: + + insert_count = (insert_count << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + insert_count += 0xff; + state = sw_delta_base; + break; + + case sw_delta_base: + + sign = (ch & 0x80) ? 1 : 0; + delta_base = ch & 0x7f; + + if (delta_base != 0x7f) { + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 header block " + "insert_count:%ui, sign:%ui, delta_base:%ui", + insert_count, sign, delta_base); + goto done; + } + + delta_base = 0; + state = sw_read_delta_base; + break; + + case sw_read_delta_base: + + delta_base = (delta_base << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + delta_base += 0x7f; + + ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 header block " + "insert_count:%ui, sign:%ui, delta_base:%ui", + insert_count, sign, delta_base); + goto done; + + case sw_header: + + index = 0; + huffman = 0; + ngx_str_null(&name); + ngx_str_null(&value); + + if (ch & 0x80) { + /* Indexed Header Field */ + + dynamic = (ch & 0x40) ? 0 : 1; + index = ch & 0x3f; + + if (index != 0x3f) { + goto done; + } + + index = 0; + state = sw_header_ri; + break; + } + + if (ch & 0x40) { + /* Literal Header Field With Name Reference */ + + dynamic = (ch & 0x10) ? 0 : 1; + index = ch & 0x0f; + + if (index != 0x0f) { + state = sw_header_value_len; + break; + } + + index = 0; + state = sw_header_lri; + break; + } + + if (ch & 0x20) { + /* Literal Header Field Without Name Reference */ + + huffman = (ch & 0x08) ? 1 : 0; + name.len = ch & 0x07; + + if (name.len == 0) { + goto failed; + } + + if (name.len != 0x07) { + offset = 0; + state = sw_header_l_name; + break; + } + + name.len = 0; + state = sw_header_l_name_len; + break; + } + + if (ch & 10) { + /* Indexed Header Field With Post-Base Index */ + + dynamic = 2; + index = ch & 0x0f; + + if (index != 0x0f) { + goto done; + } + + index = 0; + state = sw_header_pbi; + break; + } + + /* Literal Header Field With Post-Base Name Reference */ + + dynamic = 2; + index = ch & 0x07; + + if (index != 0x07) { + state = sw_header_value_len; + break; + } + + index = 0; + state = sw_header_lpbi; + break; + + case sw_header_ri: + + index = (index << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + index += 0x3f; + goto done; + + case sw_header_pbi: + + index = (index << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + index += 0x0f; + goto done; + + case sw_header_lri: + + index = (index << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + index += 0x0f; + state = sw_header_value_len; + break; + + case sw_header_lpbi: + + index = (index << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + index += 0x07; + state = sw_header_value_len; + break; + + + case sw_header_l_name_len: + + name.len = (name.len << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + name.len += 0x07; + offset = 0; + state = sw_header_l_name; + break; + + case sw_header_l_name: + if (offset++ == 0) { + name.data = p; + } + + if (offset != name.len) { + break; + } + + if (huffman) { + if (ngx_http_v3_decode_huffman(c, &name) != NGX_OK) { + goto failed; + } + } + + state = sw_header_value_len; + break; + + case sw_header_value_len: + + huffman = (ch & 0x80) ? 1 : 0; + value.len = ch & 0x7f; + + if (value.len == 0) { + value.data = p; + goto done; + } + + if (value.len != 0x7f) { + offset = 0; + state = sw_header_value; + break; + } + + value.len = 0; + state = sw_header_read_value_len; + break; + + case sw_header_read_value_len: + + value.len = (value.len << 7) + (ch & 0x7f); + if (ch & 0x80) { + break; + } + + value.len += 0x7f; + offset = 0; + state = sw_header_value; + break; + + case sw_header_value: + + if (offset++ == 0) { + value.data = p; + } + + if (offset != value.len) { + break; + } + + if (huffman) { + if (ngx_http_v3_decode_huffman(c, &value) != NGX_OK) { + goto failed; + } + } + + goto done; + + case sw_old_header: + + break; + } + } + + b->pos = p; + r->state = state; + r->h3_length = length; + r->h3_index = index; + r->h3_insert_count = insert_count; + r->h3_sign = sign; + r->h3_delta_base = delta_base; + r->h3_huffman = huffman; + r->h3_dynamic = dynamic; + r->h3_offset = offset; + + /* XXX fix large reallocations */ + r->header_name_start = name.data; + r->header_name_end = name.data + name.len; + r->header_start = value.data; + r->header_end = value.data + value.len; + + /* XXX r->lowcase_index = i; */ + + return NGX_AGAIN; + +done: + + b->pos = p + 1; + r->state = sw_header; + r->h3_length = length; + r->h3_insert_count = insert_count; + r->h3_sign = sign; + r->h3_delta_base = delta_base; + + if (state < sw_header) { + if (ngx_http_v3_check_insert_count(c, insert_count) != NGX_OK) { + return NGX_DONE; + } + + goto again; + } + + if (sign == 0) { + base = insert_count + delta_base; + } else { + base = insert_count - delta_base - 1; + } + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 header %s[%ui], base:%ui, \"%V\":\"%V\"", + dynamic ? "dynamic" : "static", index, base, &name, &value); + + if (name.data == NULL) { + + if (dynamic == 2) { + index = base - index - 1; + } else if (dynamic == 1) { + index += base; + } + + h = ngx_http_v3_lookup_table(c, dynamic, index); + if (h == NULL) { + goto failed; + } + + name = h->name; + + if (value.data == NULL) { + value = h->value; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 header \"%V\":\"%V\"", &name, &value); + + if (pseudo) { + rc = ngx_http_v3_process_pseudo_header(r, &name, &value); + + if (rc == NGX_ERROR) { + goto failed; + } + + if (rc == NGX_OK) { + r->request_end = p + 1; + goto again; + } + + /* rc == NGX_DONE */ + + r->state = sw_old_header; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 header left:%ui", length); + + r->header_name_start = name.data; + r->header_name_end = name.data + name.len; + r->header_start = value.data; + r->header_end = value.data + value.len; + r->header_hash = ngx_hash_key(name.data, name.len); /* XXX */ + + /* XXX r->lowcase_index = i; */ + + return NGX_OK; + +failed: + + return NGX_HTTP_PARSE_INVALID_REQUEST; +} + + +static ngx_int_t +ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + ngx_uint_t i; + ngx_connection_t *c; + + c = r->connection; + + if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { + r->method_start = value->data; + r->method_end = value->data + value->len; + + for (i = 0; i < sizeof(ngx_http_v3_methods) + / sizeof(ngx_http_v3_methods[0]); i++) + { + if (value->len == ngx_http_v3_methods[i].name.len + && ngx_strncmp(value->data, ngx_http_v3_methods[i].name.data, + value->len) == 0) + { + r->method = ngx_http_v3_methods[i].method; + break; + } + } + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 method \"%V\" %ui", value, r->method); + return NGX_OK; + } + + if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { + r->uri_start = value->data; + r->uri_end = value->data + value->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client sent invalid :path header: \"%V\"", value); + return NGX_ERROR; + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 path \"%V\"", value); + + return NGX_OK; + } + + if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { + r->schema_start = value->data; + r->schema_end = value->data + value->len; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 schema \"%V\"", value); + + return NGX_OK; + } + + if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { + r->host_start = value->data; + r->host_end = value->data + value->len; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 authority \"%V\"", value); + + return NGX_OK; + } + + if (name->len && name->data[0] == ':') { + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 unknown pseudo header \"%V\" \"%V\"", + name, value); + return NGX_OK; + } + + return NGX_DONE; +} + + +ngx_chain_t * +ngx_http_v3_create_header(ngx_http_request_t *r) +{ + u_char *p; + size_t len, hlen, n; + ngx_buf_t *b; + ngx_uint_t i, j; + ngx_chain_t *hl, *cl, *bl; + ngx_list_part_t *part; + ngx_table_elt_t *header; + ngx_connection_t *c; + ngx_http_core_loc_conf_t *clcf; + + c = r->connection; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header"); + + /* XXX support chunked body in the chunked filter */ + if (r->headers_out.content_length_n == -1) { + return NULL; + } + + len = 0; + + if (r->headers_out.status == NGX_HTTP_OK) { + len += ngx_http_v3_encode_prefix_int(NULL, 25, 6); + + } else { + len += 3 + ngx_http_v3_encode_prefix_int(NULL, 25, 4) + + ngx_http_v3_encode_prefix_int(NULL, 3, 7); + } + + 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_prefix_int(NULL, 92, 4) + + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n; + } + + if (r->headers_out.date == NULL) { + len += ngx_http_v3_encode_prefix_int(NULL, 6, 4) + + ngx_http_v3_encode_prefix_int(NULL, ngx_cached_http_time.len, + 7) + + 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_prefix_int(NULL, 53, 4) + + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n; + } + + if (r->headers_out.content_length_n == 0) { + len += ngx_http_v3_encode_prefix_int(NULL, 4, 6); + + } else { + len += ngx_http_v3_encode_prefix_int(NULL, 4, 4) + 1 + NGX_OFF_T_LEN; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + len += ngx_http_v3_encode_prefix_int(NULL, 10, 4) + 1 + + sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT"); + } + + /* XXX location */ + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + if (clcf->gzip_vary) { + /* Vary: Accept-Encoding */ + len += ngx_http_v3_encode_prefix_int(NULL, 59, 6); + + } 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_prefix_int(NULL, header[i].key.len, 3) + + header[i].key.len + + ngx_http_v3_encode_prefix_int(NULL, header[i].value.len, 7 ) + + header[i].value.len; + } + + 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 NULL; + } + + *b->last++ = 0; + *b->last++ = 0; + + if (r->headers_out.status == NGX_HTTP_OK) { + /* :status: 200 */ + *b->last = 0xc0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 6); + + } else { + /* :status: 200 */ + *b->last = 0x70; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 4); + *b->last = 0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 3, 7); + 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; + } + + /* server */ + *b->last = 0x70; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 92, 4); + *b->last = 0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, n, 7); + b->last = ngx_cpymem(b->last, p, n); + } + + if (r->headers_out.date == NULL) { + /* date */ + *b->last = 0x70; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 6, 4); + *b->last = 0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, + ngx_cached_http_time.len, 7); + b->last = ngx_cpymem(b->last, ngx_cached_http_time.data, + 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; + } + + /* content-type: text/plain */ + *b->last = 0x70; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 53, 4); + *b->last = 0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, n, 7); + + p = b->last; + b->last = ngx_copy(b->last, r->headers_out.content_type.data, + r->headers_out.content_type.len); + + if (r->headers_out.content_type_len == r->headers_out.content_type.len + && r->headers_out.charset.len) + { + b->last = ngx_cpymem(b->last, "; charset=", + sizeof("; charset=") - 1); + b->last = ngx_copy(b->last, r->headers_out.charset.data, + r->headers_out.charset.len); + + /* update r->headers_out.content_type for possible logging */ + + r->headers_out.content_type.len = b->last - p; + r->headers_out.content_type.data = p; + } + } + + if (r->headers_out.content_length_n == 0) { + /* content-length: 0 */ + *b->last = 0xc0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 6); + + } else if (r->headers_out.content_length_n > 0) { + /* content-length: 0 */ + *b->last = 0x70; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 4); + p = b->last++; + b->last = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); + *p = b->last - p - 1; + } + + if (r->headers_out.last_modified == NULL + && r->headers_out.last_modified_time != -1) + { + /* last-modified */ + *b->last = 0x70; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 10, 4); + p = b->last++; + b->last = ngx_http_time(b->last, r->headers_out.last_modified_time); + *p = b->last - p - 1; + } + +#if (NGX_HTTP_GZIP) + if (r->gzip_vary) { + /* vary: accept-encoding */ + *b->last = 0xc0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 59, 6); + } +#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; + } + + *b->last = 0x30; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, + header[i].key.len, + 3); + for (j = 0; j < header[i].key.len; j++) { + *b->last++ = ngx_tolower(header[i].key.data[j]); + } + + *b->last = 0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, + header[i].value.len, + 7); + b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len); + } + + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + cl->next = NULL; + + n = b->last - b->pos; + + len = 1 + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(c->pool, len); + if (b == NULL) { + return NULL; + } + + *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(c->pool); + if (hl == NULL) { + return NULL; + } + + hl->buf = b; + hl->next = cl; + + hlen = 1 + ngx_http_v3_encode_varlen_int(NULL, len); + + if (r->headers_out.content_length_n >= 0) { + len = 1 + ngx_http_v3_encode_varlen_int(NULL, + r->headers_out.content_length_n); + + b = ngx_create_temp_buf(c->pool, len); + if (b == NULL) { + NULL; + } + + *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); + + bl = ngx_alloc_chain_link(c->pool); + if (bl == NULL) { + return NULL; + } + + bl->buf = b; + bl->next = NULL; + cl->next = bl; + } + + return hl; +} -- cgit v1.2.3 From 5399670fcc51b440f00a9a584654f7bcc52d3f88 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sat, 14 Mar 2020 13:18:55 +0300 Subject: Temporary fix for header null-termination in HTTP/3. --- src/http/v3/ngx_http_v3_request.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index b34eed98e..9cb351c2d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -518,6 +518,18 @@ done: } } + /* XXX ugly reallocation for the trailing '\0' */ + + p = ngx_pnalloc(c->pool, name.len + value.len + 2); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, name.data, name.len); + name.data = p; + ngx_memcpy(p + name.len + 1, value.data, value.len); + value.data = p + name.len + 1; + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header \"%V\":\"%V\"", &name, &value); -- cgit v1.2.3 From 01dc7445f0fc392edd4f4e23f4fa1af69af68e41 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 18 Mar 2020 13:46:35 +0300 Subject: Refactored HTTP/3 parser. --- src/http/v3/ngx_http_v3_request.c | 137 ++++++++++++++++++++++++++++++++------ 1 file changed, 118 insertions(+), 19 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 9cb351c2d..e6cd27183 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,15 +10,6 @@ #include -#define NGX_HTTP_V3_FRAME_DATA 0x00 -#define NGX_HTTP_V3_FRAME_HEADERS 0x01 -#define NGX_HTTP_V3_FRAME_CANCEL_PUSH 0x03 -#define NGX_HTTP_V3_FRAME_SETTINGS 0x04 -#define NGX_HTTP_V3_FRAME_PUSH_PROMISE 0x05 -#define NGX_HTTP_V3_FRAME_GOAWAY 0x07 -#define NGX_HTTP_V3_FRAME_MAX_PUSH_ID 0x0d - - static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); @@ -46,6 +37,110 @@ struct { }; +ngx_int_t +ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) +{ + ngx_int_t rc; + ngx_str_t *name, *value; + ngx_connection_t *c; + ngx_http_v3_parse_headers_t *st; + enum { + sw_start = 0, + sw_prev, + sw_headers, + sw_last, + sw_done + }; + + c = r->connection; + st = r->h3_parse; + + if (st == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header"); + + st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_headers_t)); + if (st == NULL) { + goto failed; + } + + r->h3_parse = st; + } + + switch (r->state) { + + case sw_prev: + r->state = sw_headers; + return NGX_OK; + + case sw_done: + goto done; + + case sw_last: + r->state = sw_done; + return NGX_OK; + + default: + break; + } + + for ( /* void */ ; b->pos < b->last; b->pos++) { + + rc = ngx_http_v3_parse_headers(c, st, *b->pos); + + if (rc == NGX_ERROR) { + goto failed; + } + + if (rc == NGX_AGAIN) { + continue; + } + + name = &st->header_rep.header.name; + value = &st->header_rep.header.value; + + if (r->state == sw_start + && ngx_http_v3_process_pseudo_header(r, name, value) != NGX_OK) + { + if (rc == NGX_DONE) { + r->state = sw_last; + } else { + r->state = sw_prev; + } + + } else if (rc == NGX_DONE) { + r->state = sw_done; + } + + if (r->state == sw_start) { + continue; + } + + r->header_name_start = name->data; + r->header_name_end = name->data + name->len; + r->header_start = value->data; + r->header_end = value->data + value->len; + r->header_hash = ngx_hash_key(name->data, name->len); + + /* XXX r->lowcase_index = i; */ + + return NGX_OK; + } + + return NGX_AGAIN; + +failed: + + return r->state == sw_start ? NGX_HTTP_PARSE_INVALID_REQUEST + : NGX_HTTP_PARSE_INVALID_HEADER; + +done: + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done"); + + return NGX_HTTP_PARSE_HEADER_DONE; +} + +#if 0 ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t pseudo) { @@ -167,11 +262,14 @@ again: break; } - /* fall through */ + length &= 0x3fffff; + state = sw_header_block; + break; case sw_length_3: - length &= 0x3fffff; + length = (length << 8) + ch; + length &= 0x3fffffff; state = sw_header_block; break; @@ -567,6 +665,7 @@ failed: return NGX_HTTP_PARSE_INVALID_REQUEST; } +#endif static ngx_int_t @@ -576,6 +675,10 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_uint_t i; ngx_connection_t *c; + if (name->len == 0 || name->data[0] != ':') { + return NGX_DECLINED; + } + c = r->connection; if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { @@ -635,14 +738,10 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, return NGX_OK; } - if (name->len && name->data[0] == ':') { - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 unknown pseudo header \"%V\" \"%V\"", - name, value); - return NGX_OK; - } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 unknown pseudo header \"%V\" \"%V\"", name, value); - return NGX_DONE; + return NGX_OK; } @@ -789,7 +888,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 4); *b->last = 0; b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 3, 7); - b->last = ngx_sprintf(b->last, "%03ui ", r->headers_out.status); + b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); } if (r->headers_out.server == NULL) { -- cgit v1.2.3 From 04d037b239440bf7a2f422eb4b7c4e4e7652939e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 18 Mar 2020 15:28:20 +0300 Subject: Fixed pointer increment while parsing HTTP/3 header. --- src/http/v3/ngx_http_v3_request.c | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e6cd27183..a9adcf8c2 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -83,9 +83,8 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) break; } - for ( /* void */ ; b->pos < b->last; b->pos++) { - - rc = ngx_http_v3_parse_headers(c, st, *b->pos); + while (b->pos < b->last) { + rc = ngx_http_v3_parse_headers(c, st, *b->pos++); if (rc == NGX_ERROR) { goto failed; -- cgit v1.2.3 From e63accd7bd222aebd7cf4f90aeb8cca617d01b94 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 18 Mar 2020 20:22:16 +0300 Subject: HTTP/3 $request_line variable. --- src/http/v3/ngx_http_v3_request.c | 573 +++----------------------------------- 1 file changed, 35 insertions(+), 538 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index a9adcf8c2..75488db1d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -40,6 +40,8 @@ struct { ngx_int_t ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) { + size_t n; + u_char *p; ngx_int_t rc; ngx_str_t *name, *value; ngx_connection_t *c; @@ -97,23 +99,45 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) name = &st->header_rep.header.name; value = &st->header_rep.header.value; - if (r->state == sw_start - && ngx_http_v3_process_pseudo_header(r, name, value) != NGX_OK) - { - if (rc == NGX_DONE) { - r->state = sw_last; - } else { + if (r->state == sw_start) { + + if (ngx_http_v3_process_pseudo_header(r, name, value) == NGX_OK) { + if (rc == NGX_OK) { + continue; + } + + r->state = sw_done; + + } else if (rc == NGX_OK) { r->state = sw_prev; + + } else { + r->state = sw_last; + } + + n = (r->method_end - r->method_start) + 1 + + (r->uri_end - r->uri_start) + 1 + + sizeof("HTTP/3") - 1; + + p = ngx_pnalloc(c->pool, n); + if (p == NULL) { + goto failed; } + r->request_start = p; + + p = ngx_cpymem(p, r->method_start, r->method_end - r->method_start); + *p++ = ' '; + p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); + *p++ = ' '; + p = ngx_cpymem(p, "HTTP/3", sizeof("HTTP/3") - 1); + + r->request_end = p; + } else if (rc == NGX_DONE) { r->state = sw_done; } - if (r->state == sw_start) { - continue; - } - r->header_name_start = name->data; r->header_name_end = name->data + name->len; r->header_start = value->data; @@ -139,533 +163,6 @@ done: return NGX_HTTP_PARSE_HEADER_DONE; } -#if 0 -ngx_int_t -ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t pseudo) -{ - u_char *p, ch; - ngx_str_t name, value; - ngx_int_t rc; - ngx_uint_t length, index, insert_count, sign, base, delta_base, - huffman, dynamic, offset; - ngx_connection_t *c; - ngx_http_v3_header_t *h; - enum { - sw_start = 0, - sw_length, - sw_length_1, - sw_length_2, - sw_length_3, - sw_header_block, - sw_req_insert_count, - sw_delta_base, - sw_read_delta_base, - sw_header, - sw_old_header, - sw_header_ri, - sw_header_pbi, - sw_header_lri, - sw_header_lpbi, - sw_header_l_name_len, - sw_header_l_name, - sw_header_value_len, - sw_header_read_value_len, - sw_header_value - } state; - - c = r->connection; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header, pseudo:%ui", pseudo); - - if (r->state == sw_old_header) { - r->state = sw_header; - return NGX_OK; - } - - length = r->h3_length; - index = r->h3_index; - insert_count = r->h3_insert_count; - sign = r->h3_sign; - delta_base = r->h3_delta_base; - huffman = r->h3_huffman; - dynamic = r->h3_dynamic; - offset = r->h3_offset; - - name.data = r->header_name_start; - name.len = r->header_name_end - r->header_name_start; - value.data = r->header_start; - value.len = r->header_end - r->header_start; - - if (r->state == sw_start) { - length = 1; - } - -again: - - state = r->state; - - if (state == sw_header && length == 0) { - r->state = sw_start; - return NGX_HTTP_PARSE_HEADER_DONE; - } - - for (p = b->pos; p < b->last; p++) { - - if (state >= sw_header_block && length-- == 0) { - goto failed; - } - - ch = *p; - - switch (state) { - - case sw_start: - - if (ch != NGX_HTTP_V3_FRAME_HEADERS) { - goto failed; - } - - r->request_start = p; - state = sw_length; - break; - - case sw_length: - - length = ch; - if (length & 0xc0) { - state = sw_length_1; - break; - } - - state = sw_header_block; - break; - - case sw_length_1: - - length = (length << 8) + ch; - if ((length & 0xc000) != 0x4000) { - state = sw_length_2; - break; - } - - length &= 0x3fff; - state = sw_header_block; - break; - - case sw_length_2: - - length = (length << 8) + ch; - if ((length & 0xc00000) != 0x800000) { - state = sw_length_3; - break; - } - - length &= 0x3fffff; - state = sw_header_block; - break; - - case sw_length_3: - - length = (length << 8) + ch; - length &= 0x3fffffff; - state = sw_header_block; - break; - - case sw_header_block: - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 header block length:%ui", length); - - if (ch != 0xff) { - insert_count = ch; - state = sw_delta_base; - break; - } - - insert_count = 0; - state = sw_req_insert_count; - break; - - case sw_req_insert_count: - - insert_count = (insert_count << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - insert_count += 0xff; - state = sw_delta_base; - break; - - case sw_delta_base: - - sign = (ch & 0x80) ? 1 : 0; - delta_base = ch & 0x7f; - - if (delta_base != 0x7f) { - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 header block " - "insert_count:%ui, sign:%ui, delta_base:%ui", - insert_count, sign, delta_base); - goto done; - } - - delta_base = 0; - state = sw_read_delta_base; - break; - - case sw_read_delta_base: - - delta_base = (delta_base << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - delta_base += 0x7f; - - ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 header block " - "insert_count:%ui, sign:%ui, delta_base:%ui", - insert_count, sign, delta_base); - goto done; - - case sw_header: - - index = 0; - huffman = 0; - ngx_str_null(&name); - ngx_str_null(&value); - - if (ch & 0x80) { - /* Indexed Header Field */ - - dynamic = (ch & 0x40) ? 0 : 1; - index = ch & 0x3f; - - if (index != 0x3f) { - goto done; - } - - index = 0; - state = sw_header_ri; - break; - } - - if (ch & 0x40) { - /* Literal Header Field With Name Reference */ - - dynamic = (ch & 0x10) ? 0 : 1; - index = ch & 0x0f; - - if (index != 0x0f) { - state = sw_header_value_len; - break; - } - - index = 0; - state = sw_header_lri; - break; - } - - if (ch & 0x20) { - /* Literal Header Field Without Name Reference */ - - huffman = (ch & 0x08) ? 1 : 0; - name.len = ch & 0x07; - - if (name.len == 0) { - goto failed; - } - - if (name.len != 0x07) { - offset = 0; - state = sw_header_l_name; - break; - } - - name.len = 0; - state = sw_header_l_name_len; - break; - } - - if (ch & 10) { - /* Indexed Header Field With Post-Base Index */ - - dynamic = 2; - index = ch & 0x0f; - - if (index != 0x0f) { - goto done; - } - - index = 0; - state = sw_header_pbi; - break; - } - - /* Literal Header Field With Post-Base Name Reference */ - - dynamic = 2; - index = ch & 0x07; - - if (index != 0x07) { - state = sw_header_value_len; - break; - } - - index = 0; - state = sw_header_lpbi; - break; - - case sw_header_ri: - - index = (index << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - index += 0x3f; - goto done; - - case sw_header_pbi: - - index = (index << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - index += 0x0f; - goto done; - - case sw_header_lri: - - index = (index << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - index += 0x0f; - state = sw_header_value_len; - break; - - case sw_header_lpbi: - - index = (index << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - index += 0x07; - state = sw_header_value_len; - break; - - - case sw_header_l_name_len: - - name.len = (name.len << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - name.len += 0x07; - offset = 0; - state = sw_header_l_name; - break; - - case sw_header_l_name: - if (offset++ == 0) { - name.data = p; - } - - if (offset != name.len) { - break; - } - - if (huffman) { - if (ngx_http_v3_decode_huffman(c, &name) != NGX_OK) { - goto failed; - } - } - - state = sw_header_value_len; - break; - - case sw_header_value_len: - - huffman = (ch & 0x80) ? 1 : 0; - value.len = ch & 0x7f; - - if (value.len == 0) { - value.data = p; - goto done; - } - - if (value.len != 0x7f) { - offset = 0; - state = sw_header_value; - break; - } - - value.len = 0; - state = sw_header_read_value_len; - break; - - case sw_header_read_value_len: - - value.len = (value.len << 7) + (ch & 0x7f); - if (ch & 0x80) { - break; - } - - value.len += 0x7f; - offset = 0; - state = sw_header_value; - break; - - case sw_header_value: - - if (offset++ == 0) { - value.data = p; - } - - if (offset != value.len) { - break; - } - - if (huffman) { - if (ngx_http_v3_decode_huffman(c, &value) != NGX_OK) { - goto failed; - } - } - - goto done; - - case sw_old_header: - - break; - } - } - - b->pos = p; - r->state = state; - r->h3_length = length; - r->h3_index = index; - r->h3_insert_count = insert_count; - r->h3_sign = sign; - r->h3_delta_base = delta_base; - r->h3_huffman = huffman; - r->h3_dynamic = dynamic; - r->h3_offset = offset; - - /* XXX fix large reallocations */ - r->header_name_start = name.data; - r->header_name_end = name.data + name.len; - r->header_start = value.data; - r->header_end = value.data + value.len; - - /* XXX r->lowcase_index = i; */ - - return NGX_AGAIN; - -done: - - b->pos = p + 1; - r->state = sw_header; - r->h3_length = length; - r->h3_insert_count = insert_count; - r->h3_sign = sign; - r->h3_delta_base = delta_base; - - if (state < sw_header) { - if (ngx_http_v3_check_insert_count(c, insert_count) != NGX_OK) { - return NGX_DONE; - } - - goto again; - } - - if (sign == 0) { - base = insert_count + delta_base; - } else { - base = insert_count - delta_base - 1; - } - - ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 header %s[%ui], base:%ui, \"%V\":\"%V\"", - dynamic ? "dynamic" : "static", index, base, &name, &value); - - if (name.data == NULL) { - - if (dynamic == 2) { - index = base - index - 1; - } else if (dynamic == 1) { - index += base; - } - - h = ngx_http_v3_lookup_table(c, dynamic, index); - if (h == NULL) { - goto failed; - } - - name = h->name; - - if (value.data == NULL) { - value = h->value; - } - } - - /* XXX ugly reallocation for the trailing '\0' */ - - p = ngx_pnalloc(c->pool, name.len + value.len + 2); - if (p == NULL) { - return NGX_ERROR; - } - - ngx_memcpy(p, name.data, name.len); - name.data = p; - ngx_memcpy(p + name.len + 1, value.data, value.len); - value.data = p + name.len + 1; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 header \"%V\":\"%V\"", &name, &value); - - if (pseudo) { - rc = ngx_http_v3_process_pseudo_header(r, &name, &value); - - if (rc == NGX_ERROR) { - goto failed; - } - - if (rc == NGX_OK) { - r->request_end = p + 1; - goto again; - } - - /* rc == NGX_DONE */ - - r->state = sw_old_header; - } - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 header left:%ui", length); - - r->header_name_start = name.data; - r->header_name_end = name.data + name.len; - r->header_start = value.data; - r->header_end = value.data + value.len; - r->header_hash = ngx_hash_key(name.data, name.len); /* XXX */ - - /* XXX r->lowcase_index = i; */ - - return NGX_OK; - -failed: - - return NGX_HTTP_PARSE_INVALID_REQUEST; -} -#endif - static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, @@ -675,7 +172,7 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_connection_t *c; if (name->len == 0 || name->data[0] != ':') { - return NGX_DECLINED; + return NGX_DONE; } c = r->connection; -- cgit v1.2.3 From 8ad2707d4f5c007ec308262ea03481d95eee7e02 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 19 Mar 2020 15:03:09 +0300 Subject: Fixed header creation for header_only responses in HTTP/3. --- src/http/v3/ngx_http_v3_request.c | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 75488db1d..9a2654fd4 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -259,7 +259,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header"); /* XXX support chunked body in the chunked filter */ - if (r->headers_out.content_length_n == -1) { + if (!r->header_only && r->headers_out.content_length_n == -1) { return NULL; } @@ -310,11 +310,11 @@ ngx_http_v3_create_header(ngx_http_request_t *r) + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n; } - if (r->headers_out.content_length_n == 0) { - len += ngx_http_v3_encode_prefix_int(NULL, 4, 6); - - } else { + if (r->headers_out.content_length_n > 0) { len += ngx_http_v3_encode_prefix_int(NULL, 4, 4) + 1 + NGX_OFF_T_LEN; + + } else if (r->headers_out.content_length_n == 0) { + len += ngx_http_v3_encode_prefix_int(NULL, 4, 6); } if (r->headers_out.last_modified == NULL @@ -454,18 +454,18 @@ ngx_http_v3_create_header(ngx_http_request_t *r) } } - if (r->headers_out.content_length_n == 0) { - /* content-length: 0 */ - *b->last = 0xc0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 6); - - } else if (r->headers_out.content_length_n > 0) { + if (r->headers_out.content_length_n > 0) { /* content-length: 0 */ *b->last = 0x70; b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 4); p = b->last++; b->last = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); *p = b->last - p - 1; + + } else if (r->headers_out.content_length_n == 0) { + /* content-length: 0 */ + *b->last = 0xc0; + b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 6); } if (r->headers_out.last_modified == NULL @@ -521,6 +521,10 @@ ngx_http_v3_create_header(ngx_http_request_t *r) b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len); } + if (r->header_only) { + b->last_buf = 1; + } + cl = ngx_alloc_chain_link(c->pool); if (cl == NULL) { return NULL; -- cgit v1.2.3 From 0f77eac8af88e461c1b695094e63db249841ce0e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 20 Mar 2020 23:49:42 +0300 Subject: Removed unused variable. --- src/http/v3/ngx_http_v3_request.c | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 9a2654fd4..542fc387d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -245,7 +245,7 @@ ngx_chain_t * ngx_http_v3_create_header(ngx_http_request_t *r) { u_char *p; - size_t len, hlen, n; + size_t len, n; ngx_buf_t *b; ngx_uint_t i, j; ngx_chain_t *hl, *cl, *bl; @@ -553,8 +553,6 @@ ngx_http_v3_create_header(ngx_http_request_t *r) hl->buf = b; hl->next = cl; - hlen = 1 + ngx_http_v3_encode_varlen_int(NULL, len); - if (r->headers_out.content_length_n >= 0) { len = 1 + ngx_http_v3_encode_varlen_int(NULL, r->headers_out.content_length_n); -- cgit v1.2.3 From 81f7cff632d7db267be42f5451c6e1f29d021685 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 27 Mar 2020 15:50:42 +0300 Subject: Fixed buffer overflow. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 542fc387d..b00b93ce2 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -263,7 +263,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) return NULL; } - len = 0; + len = 2; if (r->headers_out.status == NGX_HTTP_OK) { len += ngx_http_v3_encode_prefix_int(NULL, 25, 6); -- cgit v1.2.3 From 80a38580bd04f499d72ab3b6f7776e275e47a2b3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 27 Mar 2020 19:46:54 +0300 Subject: Chunked response body in HTTP/3. --- src/http/v3/ngx_http_v3_request.c | 35 ++++++++++++++++++++++++++++++----- 1 file changed, 30 insertions(+), 5 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index b00b93ce2..756a6f90d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -258,11 +258,6 @@ ngx_http_v3_create_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header"); - /* XXX support chunked body in the chunked filter */ - if (!r->header_only && r->headers_out.content_length_n == -1) { - return NULL; - } - len = 2; if (r->headers_out.status == NGX_HTTP_OK) { @@ -578,3 +573,33 @@ ngx_http_v3_create_header(ngx_http_request_t *r) return hl; } + + +ngx_chain_t * +ngx_http_v3_create_trailers(ngx_http_request_t *r) +{ + ngx_buf_t *b; + ngx_chain_t *cl; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 create trailers"); + + /* XXX */ + + b = ngx_calloc_buf(r->pool); + if (b == NULL) { + return NULL; + } + + b->last_buf = 1; + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + cl->next = NULL; + + return cl; +} -- cgit v1.2.3 From fa1e1beadca8b1ea900ec654919aea58762ab746 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 27 Mar 2020 19:41:06 +0300 Subject: Parsing HTTP/3 request body. --- src/http/v3/ngx_http_v3_request.c | 63 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 756a6f90d..911dbab36 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -241,6 +241,69 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, } +ngx_int_t +ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, + ngx_http_chunked_t *ctx) +{ + ngx_int_t rc; + ngx_connection_t *c; + ngx_http_v3_parse_data_t *st; + + c = r->connection; + st = ctx->h3_parse; + + if (st == NULL) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse request body"); + + st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_data_t)); + if (st == NULL) { + goto failed; + } + + r->h3_parse = st; + } + + if (ctx->size) { + return NGX_OK; + } + + while (b->pos < b->last) { + rc = ngx_http_v3_parse_data(c, st, *b->pos++); + + if (rc == NGX_ERROR) { + goto failed; + } + + if (rc == NGX_AGAIN) { + continue; + } + + /* rc == NGX_DONE */ + + ctx->size = st->length; + return NGX_OK; + } + + if (!b->last_buf) { + ctx->length = 1; + return NGX_AGAIN; + } + + if (st->state) { + goto failed; + } + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done"); + + return NGX_DONE; + +failed: + + return NGX_ERROR; +} + + ngx_chain_t * ngx_http_v3_create_header(ngx_http_request_t *r) { -- cgit v1.2.3 From b77fd3dc58b8398bf85d7c11901f5497f1abdf9e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 13 Apr 2020 17:54:23 +0300 Subject: HTTP/3: fixed reading request body. --- src/http/v3/ngx_http_v3_request.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 911dbab36..e0ee0c882 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -265,7 +265,8 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, } if (ctx->size) { - return NGX_OK; + ctx->length = ctx->size + 1; + return (b->pos == b->last) ? NGX_AGAIN : NGX_OK; } while (b->pos < b->last) { -- cgit v1.2.3 From 51e4e31a8dbfd856dbc1875208fa37eeb56ddacc Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 20 May 2020 15:36:24 +0300 Subject: Assorted fixes. Found by Clang Static Analyzer. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e0ee0c882..c4b313d31 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -618,7 +618,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) b = ngx_create_temp_buf(c->pool, len); if (b == NULL) { - NULL; + return NULL; } *b->last++ = NGX_HTTP_V3_FRAME_DATA; -- cgit v1.2.3 From 6abb50658fcdf6ab92a4bc9042cd7dd9bb413850 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 19 May 2020 15:29:10 +0300 Subject: HTTP/3: split header parser in two functions. The first one parses pseudo-headers and is analagous to the request line parser in HTTP/1. The second one parses regular headers and is analogous to the header parser in HTTP/1. Additionally, error handling of client passing malformed uri is now fixed. --- src/http/v3/ngx_http_v3_request.c | 142 ++++++++++++++++++++------------------ 1 file changed, 75 insertions(+), 67 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index c4b313d31..2bb627489 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -38,21 +38,14 @@ struct { ngx_int_t -ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) +ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) { - size_t n; + size_t len; u_char *p; - ngx_int_t rc; + ngx_int_t rc, n; ngx_str_t *name, *value; ngx_connection_t *c; ngx_http_v3_parse_headers_t *st; - enum { - sw_start = 0, - sw_prev, - sw_headers, - sw_last, - sw_done - }; c = r->connection; st = r->h3_parse; @@ -68,23 +61,6 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) r->h3_parse = st; } - switch (r->state) { - - case sw_prev: - r->state = sw_headers; - return NGX_OK; - - case sw_done: - goto done; - - case sw_last: - r->state = sw_done; - return NGX_OK; - - default: - break; - } - while (b->pos < b->last) { rc = ngx_http_v3_parse_headers(c, st, *b->pos++); @@ -99,68 +75,100 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) name = &st->header_rep.header.name; value = &st->header_rep.header.value; - if (r->state == sw_start) { + n = ngx_http_v3_process_pseudo_header(r, name, value); - if (ngx_http_v3_process_pseudo_header(r, name, value) == NGX_OK) { - if (rc == NGX_OK) { - continue; - } + if (n == NGX_ERROR) { + goto failed; + } - r->state = sw_done; + if (n == NGX_OK && rc == NGX_OK) { + continue; + } - } else if (rc == NGX_OK) { - r->state = sw_prev; + len = (r->method_end - r->method_start) + 1 + + (r->uri_end - r->uri_start) + 1 + + sizeof("HTTP/3") - 1; - } else { - r->state = sw_last; - } + p = ngx_pnalloc(c->pool, len); + if (p == NULL) { + goto failed; + } - n = (r->method_end - r->method_start) + 1 - + (r->uri_end - r->uri_start) + 1 - + sizeof("HTTP/3") - 1; + r->request_start = p; + + p = ngx_cpymem(p, r->method_start, r->method_end - r->method_start); + *p++ = ' '; + p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); + *p++ = ' '; + p = ngx_cpymem(p, "HTTP/3", sizeof("HTTP/3") - 1); + + r->request_end = p; + + return NGX_OK; + } + + return NGX_AGAIN; + +failed: + + return NGX_HTTP_PARSE_INVALID_REQUEST; +} - p = ngx_pnalloc(c->pool, n); - if (p == NULL) { - goto failed; - } - r->request_start = p; +ngx_int_t +ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) +{ + ngx_int_t rc; + ngx_str_t *name, *value; + ngx_connection_t *c; + ngx_http_v3_parse_headers_t *st; - p = ngx_cpymem(p, r->method_start, r->method_end - r->method_start); - *p++ = ' '; - p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); - *p++ = ' '; - p = ngx_cpymem(p, "HTTP/3", sizeof("HTTP/3") - 1); + c = r->connection; + st = r->h3_parse; - r->request_end = p; + if (st->state == 0) { + if (r->header_name_start == NULL) { + name = &st->header_rep.header.name; - } else if (rc == NGX_DONE) { - r->state = sw_done; + if (name->len && name->data[0] != ':') { + goto done; + } } - r->header_name_start = name->data; - r->header_name_end = name->data + name->len; - r->header_start = value->data; - r->header_end = value->data + value->len; - r->header_hash = ngx_hash_key(name->data, name->len); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 parse header done"); - /* XXX r->lowcase_index = i; */ + return NGX_HTTP_PARSE_HEADER_DONE; + } - return NGX_OK; + while (b->pos < b->last) { + rc = ngx_http_v3_parse_headers(c, st, *b->pos++); + + if (rc == NGX_ERROR) { + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (rc != NGX_AGAIN) { + goto done; + } } return NGX_AGAIN; -failed: +done: - return r->state == sw_start ? NGX_HTTP_PARSE_INVALID_REQUEST - : NGX_HTTP_PARSE_INVALID_HEADER; + name = &st->header_rep.header.name; + value = &st->header_rep.header.value; -done: + r->header_name_start = name->data; + r->header_name_end = name->data + name->len; + r->header_start = value->data; + r->header_end = value->data + value->len; + r->header_hash = ngx_hash_key(name->data, name->len); - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done"); + /* XXX r->lowcase_index = i; */ - return NGX_HTTP_PARSE_HEADER_DONE; + return NGX_OK; } -- cgit v1.2.3 From d25937c2b596013874fcb049f10b28270ee49e29 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 19 May 2020 15:34:00 +0300 Subject: HTTP/3: restricted symbols in header names. As per HTTP/3 draft 27, a request or response containing uppercase header field names MUST be treated as malformed. Also, existing rules applied when parsing HTTP/1 header names are also applied to HTTP/3 header names: - null character is not allowed - underscore character may or may not be treated as invalid depending on the value of "underscores_in_headers" - all non-alphanumeric characters with the exception of '-' are treated as invalid Also, the r->locase_header field is now filled while parsing an HTTP/3 header. Error logging for invalid headers is fixed as well. --- src/http/v3/ngx_http_v3_request.c | 49 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 2bb627489..59b8ce5b8 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -116,16 +116,23 @@ failed: ngx_int_t -ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b) +ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, + ngx_uint_t allow_underscores) { + u_char ch; ngx_int_t rc; ngx_str_t *name, *value; + ngx_uint_t hash, i, n; ngx_connection_t *c; ngx_http_v3_parse_headers_t *st; c = r->connection; st = r->h3_parse; + if (st->header_rep.state == 0) { + r->invalid_header = 0; + } + if (st->state == 0) { if (r->header_name_start == NULL) { name = &st->header_rep.header.name; @@ -164,9 +171,45 @@ done: r->header_name_end = name->data + name->len; r->header_start = value->data; r->header_end = value->data + value->len; - r->header_hash = ngx_hash_key(name->data, name->len); - /* XXX r->lowcase_index = i; */ + hash = 0; + i = 0; + + for (n = 0; n < name->len; n++) { + ch = name->data[n]; + + if (ch >= 'A' && ch <= 'Z') { + /* + * A request or response containing uppercase + * header field names MUST be treated as malformed + */ + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (ch == '\0') { + return NGX_HTTP_PARSE_INVALID_HEADER; + } + + if (ch == '_' && !allow_underscores) { + r->invalid_header = 1; + continue; + } + + if ((ch < 'a' || ch > 'z') + && (ch < '0' || ch > '9') + && ch != '-' && ch != '_') + { + r->invalid_header = 1; + continue; + } + + hash = ngx_hash(hash, ch); + r->lowcase_header[i++] = ch; + i &= (NGX_HTTP_LC_HEADER_LEN - 1); + } + + r->header_hash = hash; + r->lowcase_index = i; return NGX_OK; } -- cgit v1.2.3 From d6b6b6dfc57c916dc7990504fffe71248421456d Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 19 May 2020 15:47:37 +0300 Subject: Fixed $request_length for HTTP/3. New field r->parse_start is introduced to substitute r->request_start and r->header_name_start for request length accounting. These fields only work for this purpose in HTTP/1 because HTTP/1 request line and header line start with these values. Also, error logging is now fixed to output the right part of the request. --- src/http/v3/ngx_http_v3_request.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 59b8ce5b8..432bc8711 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -59,6 +59,7 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) } r->h3_parse = st; + r->parse_start = b->pos; } while (b->pos < b->last) { @@ -130,6 +131,7 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, st = r->h3_parse; if (st->header_rep.state == 0) { + r->parse_start = b->pos; r->invalid_header = 0; } -- cgit v1.2.3 From 94764fda6efa48fc18b2fc0a2cd82d451e947094 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 19 May 2020 16:20:33 +0300 Subject: Fixed client buffer reallocation for HTTP/3. Preserving pointers within the client buffer is not needed for HTTP/3 because all data is either allocated from pool or static. Unlike with HTTP/1, data typically cannot be referenced directly within the client buffer. Trying to preserve NULLs or external pointers lead to broken pointers. Also, reverted changes in ngx_http_alloc_large_header_buffer() not relevant for HTTP/3 to minimize diff to mainstream. --- src/http/v3/ngx_http_v3_request.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 432bc8711..7fb297728 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -60,6 +60,7 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) r->h3_parse = st; r->parse_start = b->pos; + r->state = 1; } while (b->pos < b->last) { -- cgit v1.2.3 From 3168f58306820428fec2c2871fd03a325aa0c65c Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 25 Jun 2020 20:31:13 +0300 Subject: HTTP/3: do not emit a DATA frame header for header_only responses. This resulted in the frame error due to the invalid DATA frame length. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 7fb297728..d9ea99d2c 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -666,7 +666,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) hl->buf = b; hl->next = cl; - if (r->headers_out.content_length_n >= 0) { + if (r->headers_out.content_length_n >= 0 && !r->header_only) { len = 1 + ngx_http_v3_encode_varlen_int(NULL, r->headers_out.content_length_n); -- cgit v1.2.3 From 690e1bc50cf47257ce76eeed2c0392a64bee24b0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 26 Jun 2020 10:05:28 +0300 Subject: HTTP/3: fixed dropping first non-pseudo header. --- src/http/v3/ngx_http_v3_request.c | 49 ++++++++++++++++++++++++++++++--------- 1 file changed, 38 insertions(+), 11 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index d9ea99d2c..5747c8ee6 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -105,6 +105,7 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) p = ngx_cpymem(p, "HTTP/3", sizeof("HTTP/3") - 1); r->request_end = p; + r->state = 0; return NGX_OK; } @@ -127,28 +128,47 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, ngx_uint_t hash, i, n; ngx_connection_t *c; ngx_http_v3_parse_headers_t *st; + enum { + sw_start = 0, + sw_done, + sw_next, + sw_header + }; c = r->connection; st = r->h3_parse; - if (st->header_rep.state == 0) { + switch (r->state) { + + case sw_start: r->parse_start = b->pos; - r->invalid_header = 0; - } - if (st->state == 0) { - if (r->header_name_start == NULL) { - name = &st->header_rep.header.name; + if (st->state) { + r->state = sw_next; + goto done; + } - if (name->len && name->data[0] != ':') { - goto done; - } + name = &st->header_rep.header.name; + + if (name->len && name->data[0] != ':') { + r->state = sw_done; + goto done; } + /* fall through */ + + case sw_done: ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done"); - return NGX_HTTP_PARSE_HEADER_DONE; + + case sw_next: + r->parse_start = b->pos; + r->invalid_header = 0; + break; + + case sw_header: + break; } while (b->pos < b->last) { @@ -158,11 +178,18 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, return NGX_HTTP_PARSE_INVALID_HEADER; } - if (rc != NGX_AGAIN) { + if (rc == NGX_DONE) { + r->state = sw_done; + goto done; + } + + if (rc == NGX_OK) { + r->state = sw_next; goto done; } } + r->state = sw_header; return NGX_AGAIN; done: -- cgit v1.2.3 From a687d08062d8cb029ab82249aa55833cf44be3ce Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 2 Jul 2020 15:34:05 +0300 Subject: HTTP/3: refactored dynamic table implementation. Previously dynamic table was not functional because of zero limit on its size set by default. Now the following changes enable it: - new directives to set SETTINGS_QPACK_MAX_TABLE_CAPACITY and SETTINGS_QPACK_BLOCKED_STREAMS - send settings with SETTINGS_QPACK_MAX_TABLE_CAPACITY and SETTINGS_QPACK_BLOCKED_STREAMS to the client - send Insert Count Increment to the client - send Header Acknowledgement to the client - evict old dynamic table entries on overflow - decode Required Insert Count from client - block stream if Required Insert Count is not reached --- src/http/v3/ngx_http_v3_request.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 5747c8ee6..ae65ba9ea 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -64,12 +64,18 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) } while (b->pos < b->last) { - rc = ngx_http_v3_parse_headers(c, st, *b->pos++); + rc = ngx_http_v3_parse_headers(c, st, *b->pos); if (rc == NGX_ERROR) { goto failed; } + if (rc == NGX_BUSY) { + return NGX_BUSY; + } + + b->pos++; + if (rc == NGX_AGAIN) { continue; } -- cgit v1.2.3 From 707117276ed252e39c75769a140cbac6e18eb74a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 2 Jul 2020 16:47:51 +0300 Subject: HTTP/3: close QUIC connection with HTTP/QPACK errors when needed. Previously errors led only to closing streams. To simplify closing QUIC connection from a QUIC stream context, new macro ngx_http_v3_finalize_connection() is introduced. It calls ngx_quic_finalize_connection() for the parent connection. --- src/http/v3/ngx_http_v3_request.c | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index ae65ba9ea..0ffa8927d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -66,6 +66,12 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) while (b->pos < b->last) { rc = ngx_http_v3_parse_headers(c, st, *b->pos); + if (rc > 0) { + ngx_http_v3_finalize_connection(c, rc, + "could not parse request headers"); + goto failed; + } + if (rc == NGX_ERROR) { goto failed; } @@ -180,6 +186,12 @@ ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, while (b->pos < b->last) { rc = ngx_http_v3_parse_headers(c, st, *b->pos++); + if (rc > 0) { + ngx_http_v3_finalize_connection(c, rc, + "could not parse request headers"); + return NGX_HTTP_PARSE_INVALID_HEADER; + } + if (rc == NGX_ERROR) { return NGX_HTTP_PARSE_INVALID_HEADER; } @@ -359,6 +371,12 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, while (b->pos < b->last) { rc = ngx_http_v3_parse_data(c, st, *b->pos++); + if (rc > 0) { + ngx_http_v3_finalize_connection(c, rc, + "could not parse request body"); + goto failed; + } + if (rc == NGX_ERROR) { goto failed; } -- cgit v1.2.3 From 04b2a169a47ac5054350e670bc5d6fd10f2434fc Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 13 Jul 2020 16:00:00 +0300 Subject: HTTP/3: header encoding functions. --- src/http/v3/ngx_http_v3_request.c | 176 +++++++++++++++++++------------------- 1 file changed, 86 insertions(+), 90 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 0ffa8927d..5fa232a91 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,6 +10,16 @@ #include +/* static table indices */ +#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_STATUS_200 25 +#define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 +#define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 +#define NGX_HTTP_V3_HEADER_SERVER 92 + + static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); @@ -416,7 +426,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) u_char *p; size_t len, n; ngx_buf_t *b; - ngx_uint_t i, j; + ngx_uint_t i; ngx_chain_t *hl, *cl, *bl; ngx_list_part_t *part; ngx_table_elt_t *header; @@ -427,14 +437,16 @@ ngx_http_v3_create_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header"); - len = 2; + len = ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); if (r->headers_out.status == NGX_HTTP_OK) { - len += ngx_http_v3_encode_prefix_int(NULL, 25, 6); + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200); } else { - len += 3 + ngx_http_v3_encode_prefix_int(NULL, 25, 4) - + ngx_http_v3_encode_prefix_int(NULL, 3, 7); + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); } clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); @@ -450,15 +462,14 @@ ngx_http_v3_create_header(ngx_http_request_t *r) n = sizeof("nginx") - 1; } - len += ngx_http_v3_encode_prefix_int(NULL, 92, 4) - + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n; + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SERVER, + NULL, n); } if (r->headers_out.date == NULL) { - len += ngx_http_v3_encode_prefix_int(NULL, 6, 4) - + ngx_http_v3_encode_prefix_int(NULL, ngx_cached_http_time.len, - 7) - + ngx_cached_http_time.len; + len += ngx_http_v3_encode_header_lri(NULL, 0, NGX_HTTP_V3_HEADER_DATE, + NULL, ngx_cached_http_time.len); } if (r->headers_out.content_type.len) { @@ -470,22 +481,29 @@ ngx_http_v3_create_header(ngx_http_request_t *r) n += sizeof("; charset=") - 1 + r->headers_out.charset.len; } - len += ngx_http_v3_encode_prefix_int(NULL, 53, 4) - + ngx_http_v3_encode_prefix_int(NULL, n, 7) + n; + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + NULL, n); } - if (r->headers_out.content_length_n > 0) { - len += ngx_http_v3_encode_prefix_int(NULL, 4, 4) + 1 + NGX_OFF_T_LEN; + if (r->headers_out.content_length == NULL) { + if (r->headers_out.content_length_n > 0) { + len += ngx_http_v3_encode_header_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_prefix_int(NULL, 4, 6); + } else if (r->headers_out.content_length_n == 0) { + len += ngx_http_v3_encode_header_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_prefix_int(NULL, 10, 4) + 1 - + sizeof("Last-Modified: Mon, 28 Sep 1970 06:00:00 GMT"); + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, + sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); } /* XXX location */ @@ -493,8 +511,8 @@ ngx_http_v3_create_header(ngx_http_request_t *r) #if (NGX_HTTP_GZIP) if (r->gzip_vary) { if (clcf->gzip_vary) { - /* Vary: Accept-Encoding */ - len += ngx_http_v3_encode_prefix_int(NULL, 59, 6); + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); } else { r->gzip_vary = 0; @@ -521,10 +539,8 @@ ngx_http_v3_create_header(ngx_http_request_t *r) continue; } - len += ngx_http_v3_encode_prefix_int(NULL, header[i].key.len, 3) - + header[i].key.len - + ngx_http_v3_encode_prefix_int(NULL, header[i].value.len, 7 ) - + header[i].value.len; + len += ngx_http_v3_encode_header_l(NULL, &header[i].key, + &header[i].value); } ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 header len:%uz", len); @@ -534,20 +550,17 @@ ngx_http_v3_create_header(ngx_http_request_t *r) return NULL; } - *b->last++ = 0; - *b->last++ = 0; + b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last, + 0, 0, 0); if (r->headers_out.status == NGX_HTTP_OK) { - /* :status: 200 */ - *b->last = 0xc0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 6); + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200); } else { - /* :status: 200 */ - *b->last = 0x70; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 25, 4); - *b->last = 0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 3, 7); + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_STATUS_200, + NULL, 3); b->last = ngx_sprintf(b->last, "%03ui", r->headers_out.status); } @@ -565,23 +578,16 @@ ngx_http_v3_create_header(ngx_http_request_t *r) n = sizeof("nginx") - 1; } - /* server */ - *b->last = 0x70; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 92, 4); - *b->last = 0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, n, 7); - b->last = ngx_cpymem(b->last, p, n); + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_SERVER, + p, n); } if (r->headers_out.date == NULL) { - /* date */ - *b->last = 0x70; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 6, 4); - *b->last = 0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, - ngx_cached_http_time.len, 7); - b->last = ngx_cpymem(b->last, ngx_cached_http_time.data, - ngx_cached_http_time.len); + b->last = (u_char *) ngx_http_v3_encode_header_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) { @@ -593,23 +599,21 @@ ngx_http_v3_create_header(ngx_http_request_t *r) n += sizeof("; charset=") - 1 + r->headers_out.charset.len; } - /* content-type: text/plain */ - *b->last = 0x70; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 53, 4); - *b->last = 0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, n, 7); + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, + NULL, n); p = b->last; - b->last = ngx_copy(b->last, r->headers_out.content_type.data, - r->headers_out.content_type.len); + b->last = ngx_cpymem(b->last, r->headers_out.content_type.data, + r->headers_out.content_type.len); if (r->headers_out.content_type_len == r->headers_out.content_type.len && r->headers_out.charset.len) { b->last = ngx_cpymem(b->last, "; charset=", sizeof("; charset=") - 1); - b->last = ngx_copy(b->last, r->headers_out.charset.data, - r->headers_out.charset.len); + b->last = ngx_cpymem(b->last, r->headers_out.charset.data, + r->headers_out.charset.len); /* update r->headers_out.content_type for possible logging */ @@ -618,36 +622,38 @@ ngx_http_v3_create_header(ngx_http_request_t *r) } } - if (r->headers_out.content_length_n > 0) { - /* content-length: 0 */ - *b->last = 0x70; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 4); - p = b->last++; - b->last = ngx_sprintf(b->last, "%O", r->headers_out.content_length_n); - *p = b->last - p - 1; + if (r->headers_out.content_length == NULL) { + 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_header_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 if (r->headers_out.content_length_n == 0) { - /* content-length: 0 */ - *b->last = 0xc0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 4, 6); + } else if (r->headers_out.content_length_n == 0) { + b->last = (u_char *) ngx_http_v3_encode_header_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) { - /* last-modified */ - *b->last = 0x70; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 10, 4); - p = b->last++; + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, + sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); + b->last = ngx_http_time(b->last, r->headers_out.last_modified_time); - *p = b->last - p - 1; } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { - /* vary: accept-encoding */ - *b->last = 0xc0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, 59, 6); + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING); } #endif @@ -670,19 +676,9 @@ ngx_http_v3_create_header(ngx_http_request_t *r) continue; } - *b->last = 0x30; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, - header[i].key.len, - 3); - for (j = 0; j < header[i].key.len; j++) { - *b->last++ = ngx_tolower(header[i].key.data[j]); - } - - *b->last = 0; - b->last = (u_char *) ngx_http_v3_encode_prefix_int(b->last, - header[i].value.len, - 7); - b->last = ngx_copy(b->last, header[i].value.data, header[i].value.len); + b->last = (u_char *) ngx_http_v3_encode_header_l(b->last, + &header[i].key, + &header[i].value); } if (r->header_only) { -- cgit v1.2.3 From fc5a7234b469cf939c7e44b76ca1146ac7ecf519 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 23 Jul 2020 12:31:40 +0300 Subject: HTTP/3: generate Location response header for absolute redirects. --- src/http/v3/ngx_http_v3_request.c | 83 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 81 insertions(+), 2 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 5fa232a91..0edd8f514 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -14,6 +14,7 @@ #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_STATUS_200 25 #define NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN 53 #define NGX_HTTP_V3_HEADER_VARY_ACCEPT_ENCODING 59 @@ -426,12 +427,15 @@ ngx_http_v3_create_header(ngx_http_request_t *r) u_char *p; size_t len, n; ngx_buf_t *b; - ngx_uint_t i; + ngx_str_t host; + ngx_uint_t i, port; ngx_chain_t *hl, *cl, *bl; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *c; ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + u_char addr[NGX_SOCKADDR_STRLEN]; c = r->connection; @@ -506,7 +510,52 @@ ngx_http_v3_create_header(ngx_http_request_t *r) sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); } - /* XXX location */ + if (r->headers_out.location + && r->headers_out.location->value.len + && r->headers_out.location->value.data[0] == '/' + && clcf->absolute_redirect) + { + r->headers_out.location->hash = 0; + + 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 NULL; + } + } + + port = ngx_inet_get_port(c->local_sockaddr); + + n = 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) { + n += sizeof(":65535") - 1; + } + + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_LOCATION, NULL, n); + + } else { + ngx_str_null(&host); + port = 0; + } #if (NGX_HTTP_GZIP) if (r->gzip_vary) { @@ -650,6 +699,36 @@ ngx_http_v3_create_header(ngx_http_request_t *r) b->last = ngx_http_time(b->last, r->headers_out.last_modified_time); } + if (host.data) { + n = sizeof("https://") - 1 + host.len + + r->headers_out.location->value.len; + + if (port) { + n += ngx_sprintf(b->last, ":%ui", port) - b->last; + } + + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_LOCATION, + NULL, n); + + p = b->last; + b->last = ngx_cpymem(b->last, "https://", sizeof("https://") - 1); + b->last = ngx_cpymem(b->last, host.data, host.len); + + if (port) { + b->last = ngx_sprintf(b->last, ":%ui", port); + } + + b->last = ngx_cpymem(b->last, 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 = b->last - p; + r->headers_out.location->value.data = p; + ngx_str_set(&r->headers_out.location->key, "Location"); + } + #if (NGX_HTTP_GZIP) if (r->gzip_vary) { b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, -- cgit v1.2.3 From 6d7ddb54711c00e6a43ae16c9151ad9d9c89a86c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 13 Jul 2020 12:33:00 +0300 Subject: HTTP/3: encode frame ids with ngx_http_v3_encode_varlen_int(). Even though typically frame ids fit into a single byte, calling ngx_http_v3_encode_varlen_int() adds to the code clarity. --- src/http/v3/ngx_http_v3_request.c | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 0edd8f514..af9cbd2b3 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -774,14 +774,16 @@ ngx_http_v3_create_header(ngx_http_request_t *r) n = b->last - b->pos; - len = 1 + ngx_http_v3_encode_varlen_int(NULL, 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(c->pool, len); if (b == NULL) { return NULL; } - *b->last++ = NGX_HTTP_V3_FRAME_HEADERS; + 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(c->pool); @@ -793,7 +795,8 @@ ngx_http_v3_create_header(ngx_http_request_t *r) hl->next = cl; if (r->headers_out.content_length_n >= 0 && !r->header_only) { - len = 1 + ngx_http_v3_encode_varlen_int(NULL, + 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(c->pool, len); @@ -801,7 +804,8 @@ ngx_http_v3_create_header(ngx_http_request_t *r) return NULL; } - *b->last++ = NGX_HTTP_V3_FRAME_DATA; + 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); -- cgit v1.2.3 From 5e036a6bef567bbe4e87ce7958ab76663ed3242e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 14 Jul 2020 16:52:44 +0300 Subject: HTTP/3: support $server_protocol variable. Now it holds "HTTP/3.0". Previously it was empty. --- src/http/v3/ngx_http_v3_request.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index af9cbd2b3..adc40d368 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -110,6 +110,8 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) continue; } + ngx_str_set(&r->http_protocol, "HTTP/3.0"); + len = (r->method_end - r->method_start) + 1 + (r->uri_end - r->uri_start) + 1 + sizeof("HTTP/3") - 1; -- cgit v1.2.3 From 6d064c94e0600f7ff15e2815784f9f89cc4858b4 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 23 Jul 2020 13:41:24 +0300 Subject: HTTP/3: server pushes. New directives are added: - http3_max_concurrent_pushes - http3_push - http3_push_preload --- src/http/v3/ngx_http_v3_request.c | 704 +++++++++++++++++++++++++++++++++++++- 1 file changed, 697 insertions(+), 7 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index adc40d368..a0259be11 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -11,18 +11,37 @@ /* 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 static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r, + ngx_chain_t ***out); +static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, + ngx_str_t *path, ngx_chain_t ***out); +static ngx_int_t ngx_http_v3_create_push_request( + ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id); +static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r, + const char *name, ngx_str_t *value); +static void ngx_http_v3_push_request_handler(ngx_event_t *ev); +static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, + ngx_str_t *path, uint64_t push_id); struct { @@ -431,7 +450,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) ngx_buf_t *b; ngx_str_t host; ngx_uint_t i, port; - ngx_chain_t *hl, *cl, *bl; + ngx_chain_t *out, *hl, *cl, **ll; ngx_list_part_t *part; ngx_table_elt_t *header; ngx_connection_t *c; @@ -443,6 +462,17 @@ ngx_http_v3_create_header(ngx_http_request_t *r) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header"); + out = NULL; + ll = &out; + + if ((c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 + && r->method != NGX_HTTP_HEAD) + { + if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { + return NULL; + } + } + len = ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); if (r->headers_out.status == NGX_HTTP_OK) { @@ -796,6 +826,9 @@ ngx_http_v3_create_header(ngx_http_request_t *r) hl->buf = b; hl->next = cl; + *ll = hl; + ll = &cl->next; + if (r->headers_out.content_length_n >= 0 && !r->header_only) { len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_DATA) + ngx_http_v3_encode_varlen_int(NULL, @@ -811,17 +844,18 @@ ngx_http_v3_create_header(ngx_http_request_t *r) b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, r->headers_out.content_length_n); - bl = ngx_alloc_chain_link(c->pool); - if (bl == NULL) { + cl = ngx_alloc_chain_link(c->pool); + if (cl == NULL) { return NULL; } - bl->buf = b; - bl->next = NULL; - cl->next = bl; + cl->buf = b; + cl->next = NULL; + + *ll = cl; } - return hl; + return out; } @@ -853,3 +887,659 @@ ngx_http_v3_create_trailers(ngx_http_request_t *r) return cl; } + + +static ngx_int_t +ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out) +{ + u_char *start, *end, *last; + ngx_str_t path; + ngx_int_t rc; + ngx_uint_t i, push; + ngx_table_elt_t **h; + ngx_http_v3_loc_conf_t *h3lcf; + ngx_http_complex_value_t *pushes; + + h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module); + + if (h3lcf->pushes) { + pushes = h3lcf->pushes->elts; + + for (i = 0; i < h3lcf->pushes->nelts; i++) { + + if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { + return NGX_ERROR; + } + + if (path.len == 0) { + continue; + } + + if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { + continue; + } + + rc = ngx_http_v3_push_resource(r, &path, out); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_ABORT) { + return NGX_OK; + } + + /* NGX_OK, NGX_DECLINED */ + } + } + + if (!h3lcf->push_preload) { + return NGX_OK; + } + + h = r->headers_out.link.elts; + + for (i = 0; i < r->headers_out.link.nelts; i++) { + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 parse link: \"%V\"", &h[i]->value); + + start = h[i]->value.data; + end = h[i]->value.data + h[i]->value.len; + + next_link: + + while (start < end && *start == ' ') { start++; } + + if (start == end || *start++ != '<') { + continue; + } + + while (start < end && *start == ' ') { start++; } + + for (last = start; last < end && *last != '>'; last++) { + /* void */ + } + + if (last == start || last == end) { + continue; + } + + path.len = last - start; + path.data = start; + + start = last + 1; + + while (start < end && *start == ' ') { start++; } + + if (start == end) { + continue; + } + + if (*start == ',') { + start++; + goto next_link; + } + + if (*start++ != ';') { + continue; + } + + last = ngx_strlchr(start, end, ','); + + if (last == NULL) { + last = end; + } + + push = 0; + + for ( ;; ) { + + while (start < last && *start == ' ') { start++; } + + if (last - start >= 6 + && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) + { + start += 6; + + if (start == last || *start == ' ' || *start == ';') { + push = 0; + break; + } + + goto next_param; + } + + if (last - start >= 11 + && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) + { + start += 11; + + if (start == last || *start == ' ' || *start == ';') { + push = 1; + } + + goto next_param; + } + + if (last - start >= 4 + && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) + { + start += 4; + + while (start < last && *start == ' ') { start++; } + + if (start == last || *start++ != '"') { + goto next_param; + } + + for ( ;; ) { + + while (start < last && *start == ' ') { start++; } + + if (last - start >= 7 + && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) + { + start += 7; + + if (start < last && (*start == ' ' || *start == '"')) { + push = 1; + break; + } + } + + while (start < last && *start != ' ' && *start != '"') { + start++; + } + + if (start == last) { + break; + } + + if (*start == '"') { + break; + } + + start++; + } + } + + next_param: + + start = ngx_strlchr(start, last, ';'); + + if (start == NULL) { + break; + } + + start++; + } + + if (push) { + while (path.len && path.data[path.len - 1] == ' ') { + path.len--; + } + } + + if (push && path.len + && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) + { + rc = ngx_http_v3_push_resource(r, &path, out); + + if (rc == NGX_ERROR) { + return NGX_ERROR; + } + + if (rc == NGX_ABORT) { + return NGX_OK; + } + + /* NGX_OK, NGX_DECLINED */ + } + + if (last < end) { + start = last + 1; + goto next_link; + } + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, + ngx_chain_t ***ll) +{ + uint64_t push_id; + ngx_int_t rc; + ngx_chain_t *cl; + ngx_connection_t *c; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_v3_connection_t *h3c; + + c = r->connection; + h3c = c->qs->parent->data; + h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); + + ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 push \"%V\" pushing:%ui/%ui id:%uL/%uL", + path, h3c->npushing, h3scf->max_concurrent_pushes, + h3c->next_push_id, h3c->max_push_id); + + if (!ngx_path_separator(path->data[0])) { + ngx_log_error(NGX_LOG_WARN, c->log, 0, + "non-absolute path \"%V\" not pushed", path); + return NGX_DECLINED; + } + + if (h3c->next_push_id > h3c->max_push_id) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 abort pushes due to max_push_id"); + return NGX_ABORT; + } + + if (h3c->npushing >= h3scf->max_concurrent_pushes) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 abort pushes due to max_concurrent_pushes"); + return NGX_ABORT; + } + + push_id = h3c->next_push_id++; + + rc = ngx_http_v3_create_push_request(r, path, push_id); + if (rc != NGX_OK) { + return rc; + } + + cl = ngx_http_v3_create_push_promise(r, path, push_id); + if (cl == NULL) { + return NGX_ERROR; + } + + for (**ll = cl; **ll; *ll = &(**ll)->next); + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, + uint64_t push_id) +{ + ngx_pool_t *pool; + ngx_connection_t *c, *pc; + ngx_http_request_t *r; + ngx_http_log_ctx_t *ctx; + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; + ngx_http_v3_connection_t *h3c; + + pc = pr->connection; + + r = NULL; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, + "http3 create push request id:%uL", push_id); + + c = ngx_http_v3_create_push_stream(pc, push_id); + if (c == NULL) { + return NGX_ABORT; + } + + hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); + if (hc == NULL) { + goto failed; + } + + h3c = c->qs->parent->data; + ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t)); + c->data = hc; + + ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); + if (ctx == NULL) { + goto failed; + } + + ctx->connection = c; + ctx->request = NULL; + ctx->current_request = NULL; + + c->log->handler = ngx_http_log_error; + c->log->data = ctx; + c->log->action = "processing pushed request headers"; + + c->log_error = NGX_ERROR_INFO; + + r = ngx_http_create_request(c); + if (r == NULL) { + goto failed; + } + + c->data = r; + + ngx_str_set(&r->http_protocol, "HTTP/3.0"); + + r->method_name = ngx_http_core_get_method; + r->method = NGX_HTTP_GET; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + r->header_in = ngx_create_temp_buf(r->pool, + cscf->client_header_buffer_size); + if (r->header_in == NULL) { + goto failed; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 4, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + goto failed; + } + + r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; + + r->schema.data = ngx_pstrdup(r->pool, &pr->schema); + if (r->schema.data == NULL) { + goto failed; + } + + r->schema.len = pr->schema.len; + + r->uri_start = ngx_pstrdup(r->pool, path); + if (r->uri_start == NULL) { + goto failed; + } + + r->uri_end = r->uri_start + path->len; + + if (ngx_http_parse_uri(r) != NGX_OK) { + goto failed; + } + + if (ngx_http_process_request_uri(r) != NGX_OK) { + goto failed; + } + + if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) + != NGX_OK) + { + goto failed; + } + + if (pr->headers_in.accept_encoding) { + if (ngx_http_v3_set_push_header(r, "accept-encoding", + &pr->headers_in.accept_encoding->value) + != NGX_OK) + { + goto failed; + } + } + + if (pr->headers_in.accept_language) { + if (ngx_http_v3_set_push_header(r, "accept-language", + &pr->headers_in.accept_language->value) + != NGX_OK) + { + goto failed; + } + } + + if (pr->headers_in.user_agent) { + if (ngx_http_v3_set_push_header(r, "user-agent", + &pr->headers_in.user_agent->value) + != NGX_OK) + { + goto failed; + } + } + + c->read->handler = ngx_http_v3_push_request_handler; + c->read->handler = ngx_http_v3_push_request_handler; + + ngx_post_event(c->read, &ngx_posted_events); + + return NGX_OK; + +failed: + + if (r) { + ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + c->destroyed = 1; + + pool = c->pool; + + ngx_close_connection(c); + + ngx_destroy_pool(pool); + + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, + ngx_str_t *value) +{ + u_char *p; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 push header \"%s\": \"%V\"", name, value); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + p = ngx_pnalloc(r->pool, value->len + 1); + if (p == NULL) { + return NGX_ERROR; + } + + ngx_memcpy(p, value->data, value->len); + p[value->len] = '\0'; + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + return NGX_ERROR; + } + + h->key.data = (u_char *) name; + h->key.len = ngx_strlen(name); + h->hash = ngx_hash_key(h->key.data, h->key.len); + h->lowcase_key = (u_char *) name; + h->value.data = p; + h->value.len = value->len; + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } + + return NGX_OK; +} + + +static void +ngx_http_v3_push_request_handler(ngx_event_t *ev) +{ + ngx_connection_t *c; + ngx_http_request_t *r; + + c = ev->data; + r = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler"); + + ngx_http_process_request(r); +} + + +static ngx_chain_t * +ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, + uint64_t push_id) +{ + size_t n, len; + ngx_buf_t *b; + ngx_chain_t *hl, *cl; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 create push promise id:%uL", push_id); + + len = ngx_http_v3_encode_varlen_int(NULL, push_id); + + len += ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); + + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_METHOD_GET); + + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_AUTHORITY, + NULL, r->headers_in.server.len); + + if (path->len == 1 && path->data[0] == '/') { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT); + + } else { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT, + NULL, path->len); + } + + if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + + } else if (r->schema.len == 4 + && ngx_strncmp(r->schema.data, "http", 4) == 0) + { + len += ngx_http_v3_encode_header_ri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP); + + } else { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP, + NULL, r->schema.len); + } + + if (r->headers_in.accept_encoding) { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, + r->headers_in.accept_encoding->value.len); + } + + if (r->headers_in.accept_language) { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, + r->headers_in.accept_language->value.len); + } + + if (r->headers_in.user_agent) { + len += ngx_http_v3_encode_header_lri(NULL, 0, + NGX_HTTP_V3_HEADER_USER_AGENT, NULL, + r->headers_in.user_agent->value.len); + } + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); + + b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last, + 0, 0, 0); + + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_METHOD_GET); + + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_AUTHORITY, + r->headers_in.server.data, + r->headers_in.server.len); + + if (path->len == 1 && path->data[0] == '/') { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT); + + } else { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_PATH_ROOT, + path->data, path->len); + } + + if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTPS); + + } else if (r->schema.len == 4 + && ngx_strncmp(r->schema.data, "http", 4) == 0) + { + b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP); + + } else { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_SCHEME_HTTP, + r->schema.data, r->schema.len); + } + + if (r->headers_in.accept_encoding) { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, + r->headers_in.accept_encoding->value.data, + r->headers_in.accept_encoding->value.len); + } + + if (r->headers_in.accept_language) { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, + r->headers_in.accept_language->value.data, + r->headers_in.accept_language->value.len); + } + + if (r->headers_in.user_agent) { + b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, + NGX_HTTP_V3_HEADER_USER_AGENT, + r->headers_in.user_agent->value.data, + r->headers_in.user_agent->value.len); + } + + cl = ngx_alloc_chain_link(r->pool); + if (cl == NULL) { + return NULL; + } + + cl->buf = b; + cl->next = NULL; + + n = b->last - b->pos; + + len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) + + ngx_http_v3_encode_varlen_int(NULL, n); + + b = ngx_create_temp_buf(r->pool, len); + if (b == NULL) { + return NULL; + } + + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, + NGX_HTTP_V3_FRAME_PUSH_PROMISE); + b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); + + hl = ngx_alloc_chain_link(r->pool); + if (hl == NULL) { + return NULL; + } + + hl->buf = b; + hl->next = cl; + + return hl; +} -- cgit v1.2.3 From 9a0fb643bfcea20f8a3ede222f9a1d8ec234a2cd Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 18 Aug 2020 17:11:32 +0300 Subject: HTTP/3: fixed context storage in request body parser. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index a0259be11..24ad771d6 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -392,7 +392,7 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, goto failed; } - r->h3_parse = st; + ctx->h3_parse = st; } if (ctx->size) { -- cgit v1.2.3 From 46173bd4b40023fd5e35bfe77b9ac2205e0c6bb0 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 16 Sep 2020 18:59:25 +0100 Subject: HTTP/3: fixed handling request body eof. While for HTTP/1 unexpected eof always means an error, for HTTP/3 an eof right after a DATA frame end means the end of the request body. For this reason, since adding HTTP/3 support, eof no longer produced an error right after recv() but was passed to filters which would make a decision. This decision was made in ngx_http_parse_chunked() and ngx_http_v3_parse_request_body() based on the b->last_buf flag. Now that since 0f7f1a509113 (1.19.2) rb->chunked->length is a lower threshold for the expected number of bytes, it can be set to zero to indicate that more bytes may or may not follow. Now it's possible to move the check for eof from parser functions to ngx_http_request_body_chunked_filter() and clean up the parsing code. Also, in the default branch, in case of eof, the following three things happened, which were replaced with returning NGX_ERROR while implementing HTTP/3: - "client prematurely closed connection" message was logged - c->error flag was set - NGX_HTTP_BAD_REQUEST was returned The change brings back this behavior for HTTP/1 as well as HTTP/3. --- src/http/v3/ngx_http_v3_request.c | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 24ad771d6..fe3c79bf0 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -379,6 +379,10 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, ngx_int_t rc; ngx_connection_t *c; ngx_http_v3_parse_data_t *st; + enum { + sw_start = 0, + sw_skip + }; c = r->connection; st = ctx->h3_parse; @@ -395,12 +399,8 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, ctx->h3_parse = st; } - if (ctx->size) { - ctx->length = ctx->size + 1; - return (b->pos == b->last) ? NGX_AGAIN : NGX_OK; - } + while (b->pos < b->last && ctx->size == 0) { - while (b->pos < b->last) { rc = ngx_http_v3_parse_data(c, st, *b->pos++); if (rc > 0) { @@ -414,27 +414,27 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, } if (rc == NGX_AGAIN) { + ctx->state = sw_skip; continue; } /* rc == NGX_DONE */ ctx->size = st->length; - return NGX_OK; + ctx->state = sw_start; } - if (!b->last_buf) { + if (ctx->state == sw_skip) { ctx->length = 1; return NGX_AGAIN; } - if (st->state) { - goto failed; + if (b->pos == b->last) { + ctx->length = ctx->size; + return NGX_AGAIN; } - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header done"); - - return NGX_DONE; + return NGX_OK; failed: -- cgit v1.2.3 From d294369915461ba764426c709301b6c66ed33681 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 24 Aug 2020 09:56:36 +0300 Subject: HTTP/3: skip unknown frames on request stream. As per HTTP/3 draft 29, section 4.1: Frames of unknown types (Section 9), including reserved frames (Section 7.2.8) MAY be sent on a request or push stream before, after, or interleaved with other frames described in this section. Also, trailers frame is now used as an indication of the request body end. --- src/http/v3/ngx_http_v3_request.c | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index fe3c79bf0..d9f4c9d55 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -418,7 +418,11 @@ ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, continue; } - /* rc == NGX_DONE */ + if (rc == NGX_DONE) { + return NGX_DONE; + } + + /* rc == NGX_OK */ ctx->size = st->length; ctx->state = sw_start; -- cgit v1.2.3 From 2fd31c8959fbae8f069d09b61f339358214e75d1 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 10 Nov 2020 19:40:00 +0000 Subject: QUIC: renamed c->qs to c->quic. --- src/http/v3/ngx_http_v3_request.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index d9f4c9d55..5511e3031 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -469,7 +469,7 @@ ngx_http_v3_create_header(ngx_http_request_t *r) out = NULL; ll = &out; - if ((c->qs->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 + if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 && r->method != NGX_HTTP_HEAD) { if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { @@ -1123,7 +1123,7 @@ ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, ngx_http_v3_connection_t *h3c; c = r->connection; - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -1196,7 +1196,7 @@ ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, goto failed; } - h3c = c->qs->parent->data; + h3c = c->quic->parent->data; ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t)); c->data = hc; -- cgit v1.2.3 From 7cfc5eb11fbfe3beb9d793dbacae70fb658d46c2 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 25 Nov 2020 17:57:43 +0000 Subject: HTTP/3: eliminated r->method_start. The field was introduced to ease parsing HTTP/3 requests. The change reduces diff to the default branch. --- src/http/v3/ngx_http_v3_request.c | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 5511e3031..2ff0440d9 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -129,11 +129,9 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) continue; } - ngx_str_set(&r->http_protocol, "HTTP/3.0"); - - len = (r->method_end - r->method_start) + 1 + len = r->method_name.len + 1 + (r->uri_end - r->uri_start) + 1 - + sizeof("HTTP/3") - 1; + + sizeof("HTTP/3.0") - 1; p = ngx_pnalloc(c->pool, len); if (p == NULL) { @@ -142,11 +140,13 @@ ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) r->request_start = p; - p = ngx_cpymem(p, r->method_start, r->method_end - r->method_start); + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + r->method_end = p - 1; *p++ = ' '; p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); *p++ = ' '; - p = ngx_cpymem(p, "HTTP/3", sizeof("HTTP/3") - 1); + r->http_protocol.data = p; + p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1); r->request_end = p; r->state = 0; @@ -309,8 +309,7 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, c = r->connection; if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { - r->method_start = value->data; - r->method_end = value->data + value->len; + r->method_name = *value; for (i = 0; i < sizeof(ngx_http_v3_methods) / sizeof(ngx_http_v3_methods[0]); i++) -- cgit v1.2.3 From 4b440cbf97af3ffe0ab31cb083fb1ce5b0fb5f89 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 27 Nov 2020 17:46:21 +0000 Subject: HTTP/3: introduced ngx_http_v3_filter. The filter is responsible for creating HTTP/3 response header and body. The change removes differences to the default branch for ngx_http_chunked_filter_module and ngx_http_header_filter_module. --- src/http/v3/ngx_http_v3_request.c | 1133 ------------------------------------- 1 file changed, 1133 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 2ff0440d9..2b50133f1 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,38 +10,8 @@ #include -/* 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 - - static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); -static ngx_int_t ngx_http_v3_push_resources(ngx_http_request_t *r, - ngx_chain_t ***out); -static ngx_int_t ngx_http_v3_push_resource(ngx_http_request_t *r, - ngx_str_t *path, ngx_chain_t ***out); -static ngx_int_t ngx_http_v3_create_push_request( - ngx_http_request_t *pr, ngx_str_t *path, uint64_t push_id); -static ngx_int_t ngx_http_v3_set_push_header(ngx_http_request_t *r, - const char *name, ngx_str_t *value); -static void ngx_http_v3_push_request_handler(ngx_event_t *ev); -static ngx_chain_t *ngx_http_v3_create_push_promise(ngx_http_request_t *r, - ngx_str_t *path, uint64_t push_id); struct { @@ -443,1106 +413,3 @@ failed: return NGX_ERROR; } - - -ngx_chain_t * -ngx_http_v3_create_header(ngx_http_request_t *r) -{ - u_char *p; - size_t len, n; - ngx_buf_t *b; - ngx_str_t host; - 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_core_loc_conf_t *clcf; - ngx_http_core_srv_conf_t *cscf; - u_char addr[NGX_SOCKADDR_STRLEN]; - - c = r->connection; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 create header"); - - out = NULL; - ll = &out; - - if ((c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0 - && r->method != NGX_HTTP_HEAD) - { - if (ngx_http_v3_push_resources(r, &ll) != NGX_OK) { - return NULL; - } - } - - len = ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); - - if (r->headers_out.status == NGX_HTTP_OK) { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_STATUS_200); - - } else { - len += ngx_http_v3_encode_header_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_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_SERVER, - NULL, n); - } - - if (r->headers_out.date == NULL) { - len += ngx_http_v3_encode_header_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_header_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_header_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_header_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_header_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 - && r->headers_out.location->value.data[0] == '/' - && clcf->absolute_redirect) - { - r->headers_out.location->hash = 0; - - 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 NULL; - } - } - - port = ngx_inet_get_port(c->local_sockaddr); - - n = 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) { - n += sizeof(":65535") - 1; - } - - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_LOCATION, NULL, n); - - } else { - ngx_str_null(&host); - port = 0; - } - -#if (NGX_HTTP_GZIP) - if (r->gzip_vary) { - if (clcf->gzip_vary) { - len += ngx_http_v3_encode_header_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_header_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 NULL; - } - - b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last, - 0, 0, 0); - - if (r->headers_out.status == NGX_HTTP_OK) { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, - NGX_HTTP_V3_HEADER_STATUS_200); - - } else { - b->last = (u_char *) ngx_http_v3_encode_header_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; - } - - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_SERVER, - p, n); - } - - if (r->headers_out.date == NULL) { - b->last = (u_char *) ngx_http_v3_encode_header_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) { - 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; - } - - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_CONTENT_TYPE_TEXT_PLAIN, - NULL, n); - - p = b->last; - b->last = ngx_cpymem(b->last, r->headers_out.content_type.data, - r->headers_out.content_type.len); - - if (r->headers_out.content_type_len == r->headers_out.content_type.len - && r->headers_out.charset.len) - { - b->last = ngx_cpymem(b->last, "; charset=", - sizeof("; charset=") - 1); - b->last = ngx_cpymem(b->last, r->headers_out.charset.data, - r->headers_out.charset.len); - - /* update r->headers_out.content_type for possible logging */ - - r->headers_out.content_type.len = b->last - p; - r->headers_out.content_type.data = p; - } - } - - if (r->headers_out.content_length == NULL) { - 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_header_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 if (r->headers_out.content_length_n == 0) { - b->last = (u_char *) ngx_http_v3_encode_header_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) - { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_LAST_MODIFIED, NULL, - sizeof("Mon, 28 Sep 1970 06:00:00 GMT") - 1); - - b->last = ngx_http_time(b->last, r->headers_out.last_modified_time); - } - - if (host.data) { - n = sizeof("https://") - 1 + host.len - + r->headers_out.location->value.len; - - if (port) { - n += ngx_sprintf(b->last, ":%ui", port) - b->last; - } - - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_LOCATION, - NULL, n); - - p = b->last; - b->last = ngx_cpymem(b->last, "https://", sizeof("https://") - 1); - b->last = ngx_cpymem(b->last, host.data, host.len); - - if (port) { - b->last = ngx_sprintf(b->last, ":%ui", port); - } - - b->last = ngx_cpymem(b->last, 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 = b->last - p; - r->headers_out.location->value.data = p; - ngx_str_set(&r->headers_out.location->key, "Location"); - } - -#if (NGX_HTTP_GZIP) - if (r->gzip_vary) { - b->last = (u_char *) ngx_http_v3_encode_header_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; - } - - b->last = (u_char *) ngx_http_v3_encode_header_l(b->last, - &header[i].key, - &header[i].value); - } - - if (r->header_only) { - b->last_buf = 1; - } - - cl = ngx_alloc_chain_link(c->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - cl->next = NULL; - - n = b->last - b->pos; - - len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_HEADERS) - + ngx_http_v3_encode_varlen_int(NULL, n); - - b = ngx_create_temp_buf(c->pool, len); - if (b == NULL) { - return NULL; - } - - 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(c->pool); - if (hl == NULL) { - return NULL; - } - - hl->buf = b; - hl->next = cl; - - *ll = hl; - ll = &cl->next; - - if (r->headers_out.content_length_n >= 0 && !r->header_only) { - 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(c->pool, len); - if (b == NULL) { - return NULL; - } - - 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); - - cl = ngx_alloc_chain_link(c->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - cl->next = NULL; - - *ll = cl; - } - - return out; -} - - -ngx_chain_t * -ngx_http_v3_create_trailers(ngx_http_request_t *r) -{ - ngx_buf_t *b; - ngx_chain_t *cl; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 create trailers"); - - /* XXX */ - - b = ngx_calloc_buf(r->pool); - if (b == NULL) { - return NULL; - } - - b->last_buf = 1; - - cl = ngx_alloc_chain_link(r->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - cl->next = NULL; - - return cl; -} - - -static ngx_int_t -ngx_http_v3_push_resources(ngx_http_request_t *r, ngx_chain_t ***out) -{ - u_char *start, *end, *last; - ngx_str_t path; - ngx_int_t rc; - ngx_uint_t i, push; - ngx_table_elt_t **h; - ngx_http_v3_loc_conf_t *h3lcf; - ngx_http_complex_value_t *pushes; - - h3lcf = ngx_http_get_module_loc_conf(r, ngx_http_v3_module); - - if (h3lcf->pushes) { - pushes = h3lcf->pushes->elts; - - for (i = 0; i < h3lcf->pushes->nelts; i++) { - - if (ngx_http_complex_value(r, &pushes[i], &path) != NGX_OK) { - return NGX_ERROR; - } - - if (path.len == 0) { - continue; - } - - if (path.len == 3 && ngx_strncmp(path.data, "off", 3) == 0) { - continue; - } - - rc = ngx_http_v3_push_resource(r, &path, out); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_ABORT) { - return NGX_OK; - } - - /* NGX_OK, NGX_DECLINED */ - } - } - - if (!h3lcf->push_preload) { - return NGX_OK; - } - - h = r->headers_out.link.elts; - - for (i = 0; i < r->headers_out.link.nelts; i++) { - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 parse link: \"%V\"", &h[i]->value); - - start = h[i]->value.data; - end = h[i]->value.data + h[i]->value.len; - - next_link: - - while (start < end && *start == ' ') { start++; } - - if (start == end || *start++ != '<') { - continue; - } - - while (start < end && *start == ' ') { start++; } - - for (last = start; last < end && *last != '>'; last++) { - /* void */ - } - - if (last == start || last == end) { - continue; - } - - path.len = last - start; - path.data = start; - - start = last + 1; - - while (start < end && *start == ' ') { start++; } - - if (start == end) { - continue; - } - - if (*start == ',') { - start++; - goto next_link; - } - - if (*start++ != ';') { - continue; - } - - last = ngx_strlchr(start, end, ','); - - if (last == NULL) { - last = end; - } - - push = 0; - - for ( ;; ) { - - while (start < last && *start == ' ') { start++; } - - if (last - start >= 6 - && ngx_strncasecmp(start, (u_char *) "nopush", 6) == 0) - { - start += 6; - - if (start == last || *start == ' ' || *start == ';') { - push = 0; - break; - } - - goto next_param; - } - - if (last - start >= 11 - && ngx_strncasecmp(start, (u_char *) "rel=preload", 11) == 0) - { - start += 11; - - if (start == last || *start == ' ' || *start == ';') { - push = 1; - } - - goto next_param; - } - - if (last - start >= 4 - && ngx_strncasecmp(start, (u_char *) "rel=", 4) == 0) - { - start += 4; - - while (start < last && *start == ' ') { start++; } - - if (start == last || *start++ != '"') { - goto next_param; - } - - for ( ;; ) { - - while (start < last && *start == ' ') { start++; } - - if (last - start >= 7 - && ngx_strncasecmp(start, (u_char *) "preload", 7) == 0) - { - start += 7; - - if (start < last && (*start == ' ' || *start == '"')) { - push = 1; - break; - } - } - - while (start < last && *start != ' ' && *start != '"') { - start++; - } - - if (start == last) { - break; - } - - if (*start == '"') { - break; - } - - start++; - } - } - - next_param: - - start = ngx_strlchr(start, last, ';'); - - if (start == NULL) { - break; - } - - start++; - } - - if (push) { - while (path.len && path.data[path.len - 1] == ' ') { - path.len--; - } - } - - if (push && path.len - && !(path.len > 1 && path.data[0] == '/' && path.data[1] == '/')) - { - rc = ngx_http_v3_push_resource(r, &path, out); - - if (rc == NGX_ERROR) { - return NGX_ERROR; - } - - if (rc == NGX_ABORT) { - return NGX_OK; - } - - /* NGX_OK, NGX_DECLINED */ - } - - if (last < end) { - start = last + 1; - goto next_link; - } - } - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v3_push_resource(ngx_http_request_t *r, ngx_str_t *path, - ngx_chain_t ***ll) -{ - uint64_t push_id; - ngx_int_t rc; - ngx_chain_t *cl; - ngx_connection_t *c; - ngx_http_v3_srv_conf_t *h3scf; - ngx_http_v3_connection_t *h3c; - - c = r->connection; - h3c = c->quic->parent->data; - h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); - - ngx_log_debug5(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 push \"%V\" pushing:%ui/%ui id:%uL/%uL", - path, h3c->npushing, h3scf->max_concurrent_pushes, - h3c->next_push_id, h3c->max_push_id); - - if (!ngx_path_separator(path->data[0])) { - ngx_log_error(NGX_LOG_WARN, c->log, 0, - "non-absolute path \"%V\" not pushed", path); - return NGX_DECLINED; - } - - if (h3c->next_push_id > h3c->max_push_id) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 abort pushes due to max_push_id"); - return NGX_ABORT; - } - - if (h3c->npushing >= h3scf->max_concurrent_pushes) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 abort pushes due to max_concurrent_pushes"); - return NGX_ABORT; - } - - push_id = h3c->next_push_id++; - - rc = ngx_http_v3_create_push_request(r, path, push_id); - if (rc != NGX_OK) { - return rc; - } - - cl = ngx_http_v3_create_push_promise(r, path, push_id); - if (cl == NULL) { - return NGX_ERROR; - } - - for (**ll = cl; **ll; *ll = &(**ll)->next); - - return NGX_OK; -} - - -static ngx_int_t -ngx_http_v3_create_push_request(ngx_http_request_t *pr, ngx_str_t *path, - uint64_t push_id) -{ - ngx_pool_t *pool; - ngx_connection_t *c, *pc; - ngx_http_request_t *r; - ngx_http_log_ctx_t *ctx; - ngx_http_connection_t *hc; - ngx_http_core_srv_conf_t *cscf; - ngx_http_v3_connection_t *h3c; - - pc = pr->connection; - - r = NULL; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pc->log, 0, - "http3 create push request id:%uL", push_id); - - c = ngx_http_v3_create_push_stream(pc, push_id); - if (c == NULL) { - return NGX_ABORT; - } - - hc = ngx_palloc(c->pool, sizeof(ngx_http_connection_t)); - if (hc == NULL) { - goto failed; - } - - h3c = c->quic->parent->data; - ngx_memcpy(hc, h3c, sizeof(ngx_http_connection_t)); - c->data = hc; - - ctx = ngx_palloc(c->pool, sizeof(ngx_http_log_ctx_t)); - if (ctx == NULL) { - goto failed; - } - - ctx->connection = c; - ctx->request = NULL; - ctx->current_request = NULL; - - c->log->handler = ngx_http_log_error; - c->log->data = ctx; - c->log->action = "processing pushed request headers"; - - c->log_error = NGX_ERROR_INFO; - - r = ngx_http_create_request(c); - if (r == NULL) { - goto failed; - } - - c->data = r; - - ngx_str_set(&r->http_protocol, "HTTP/3.0"); - - r->method_name = ngx_http_core_get_method; - r->method = NGX_HTTP_GET; - - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); - - r->header_in = ngx_create_temp_buf(r->pool, - cscf->client_header_buffer_size); - if (r->header_in == NULL) { - goto failed; - } - - if (ngx_list_init(&r->headers_in.headers, r->pool, 4, - sizeof(ngx_table_elt_t)) - != NGX_OK) - { - goto failed; - } - - r->headers_in.connection_type = NGX_HTTP_CONNECTION_CLOSE; - - r->schema.data = ngx_pstrdup(r->pool, &pr->schema); - if (r->schema.data == NULL) { - goto failed; - } - - r->schema.len = pr->schema.len; - - r->uri_start = ngx_pstrdup(r->pool, path); - if (r->uri_start == NULL) { - goto failed; - } - - r->uri_end = r->uri_start + path->len; - - if (ngx_http_parse_uri(r) != NGX_OK) { - goto failed; - } - - if (ngx_http_process_request_uri(r) != NGX_OK) { - goto failed; - } - - if (ngx_http_v3_set_push_header(r, "host", &pr->headers_in.server) - != NGX_OK) - { - goto failed; - } - - if (pr->headers_in.accept_encoding) { - if (ngx_http_v3_set_push_header(r, "accept-encoding", - &pr->headers_in.accept_encoding->value) - != NGX_OK) - { - goto failed; - } - } - - if (pr->headers_in.accept_language) { - if (ngx_http_v3_set_push_header(r, "accept-language", - &pr->headers_in.accept_language->value) - != NGX_OK) - { - goto failed; - } - } - - if (pr->headers_in.user_agent) { - if (ngx_http_v3_set_push_header(r, "user-agent", - &pr->headers_in.user_agent->value) - != NGX_OK) - { - goto failed; - } - } - - c->read->handler = ngx_http_v3_push_request_handler; - c->read->handler = ngx_http_v3_push_request_handler; - - ngx_post_event(c->read, &ngx_posted_events); - - return NGX_OK; - -failed: - - if (r) { - ngx_http_free_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - } - - c->destroyed = 1; - - pool = c->pool; - - ngx_close_connection(c); - - ngx_destroy_pool(pool); - - return NGX_ERROR; -} - - -static ngx_int_t -ngx_http_v3_set_push_header(ngx_http_request_t *r, const char *name, - ngx_str_t *value) -{ - u_char *p; - ngx_table_elt_t *h; - ngx_http_header_t *hh; - ngx_http_core_main_conf_t *cmcf; - - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 push header \"%s\": \"%V\"", name, value); - - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - - p = ngx_pnalloc(r->pool, value->len + 1); - if (p == NULL) { - return NGX_ERROR; - } - - ngx_memcpy(p, value->data, value->len); - p[value->len] = '\0'; - - h = ngx_list_push(&r->headers_in.headers); - if (h == NULL) { - return NGX_ERROR; - } - - h->key.data = (u_char *) name; - h->key.len = ngx_strlen(name); - h->hash = ngx_hash_key(h->key.data, h->key.len); - h->lowcase_key = (u_char *) name; - h->value.data = p; - h->value.len = value->len; - - hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, - h->lowcase_key, h->key.len); - - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return NGX_ERROR; - } - - return NGX_OK; -} - - -static void -ngx_http_v3_push_request_handler(ngx_event_t *ev) -{ - ngx_connection_t *c; - ngx_http_request_t *r; - - c = ev->data; - r = c->data; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 push request handler"); - - ngx_http_process_request(r); -} - - -static ngx_chain_t * -ngx_http_v3_create_push_promise(ngx_http_request_t *r, ngx_str_t *path, - uint64_t push_id) -{ - size_t n, len; - ngx_buf_t *b; - ngx_chain_t *hl, *cl; - - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, - "http3 create push promise id:%uL", push_id); - - len = ngx_http_v3_encode_varlen_int(NULL, push_id); - - len += ngx_http_v3_encode_header_block_prefix(NULL, 0, 0, 0); - - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_METHOD_GET); - - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_AUTHORITY, - NULL, r->headers_in.server.len); - - if (path->len == 1 && path->data[0] == '/') { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT); - - } else { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT, - NULL, path->len); - } - - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); - - } else if (r->schema.len == 4 - && ngx_strncmp(r->schema.data, "http", 4) == 0) - { - len += ngx_http_v3_encode_header_ri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP); - - } else { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP, - NULL, r->schema.len); - } - - if (r->headers_in.accept_encoding) { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, NULL, - r->headers_in.accept_encoding->value.len); - } - - if (r->headers_in.accept_language) { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, NULL, - r->headers_in.accept_language->value.len); - } - - if (r->headers_in.user_agent) { - len += ngx_http_v3_encode_header_lri(NULL, 0, - NGX_HTTP_V3_HEADER_USER_AGENT, NULL, - r->headers_in.user_agent->value.len); - } - - b = ngx_create_temp_buf(r->pool, len); - if (b == NULL) { - return NULL; - } - - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, push_id); - - b->last = (u_char *) ngx_http_v3_encode_header_block_prefix(b->last, - 0, 0, 0); - - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, - NGX_HTTP_V3_HEADER_METHOD_GET); - - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_AUTHORITY, - r->headers_in.server.data, - r->headers_in.server.len); - - if (path->len == 1 && path->data[0] == '/') { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT); - - } else { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_PATH_ROOT, - path->data, path->len); - } - - if (r->schema.len == 5 && ngx_strncmp(r->schema.data, "https", 5) == 0) { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTPS); - - } else if (r->schema.len == 4 - && ngx_strncmp(r->schema.data, "http", 4) == 0) - { - b->last = (u_char *) ngx_http_v3_encode_header_ri(b->last, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP); - - } else { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_SCHEME_HTTP, - r->schema.data, r->schema.len); - } - - if (r->headers_in.accept_encoding) { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_ACCEPT_ENCODING, - r->headers_in.accept_encoding->value.data, - r->headers_in.accept_encoding->value.len); - } - - if (r->headers_in.accept_language) { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_ACCEPT_LANGUAGE, - r->headers_in.accept_language->value.data, - r->headers_in.accept_language->value.len); - } - - if (r->headers_in.user_agent) { - b->last = (u_char *) ngx_http_v3_encode_header_lri(b->last, 0, - NGX_HTTP_V3_HEADER_USER_AGENT, - r->headers_in.user_agent->value.data, - r->headers_in.user_agent->value.len); - } - - cl = ngx_alloc_chain_link(r->pool); - if (cl == NULL) { - return NULL; - } - - cl->buf = b; - cl->next = NULL; - - n = b->last - b->pos; - - len = ngx_http_v3_encode_varlen_int(NULL, NGX_HTTP_V3_FRAME_PUSH_PROMISE) - + ngx_http_v3_encode_varlen_int(NULL, n); - - b = ngx_create_temp_buf(r->pool, len); - if (b == NULL) { - return NULL; - } - - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, - NGX_HTTP_V3_FRAME_PUSH_PROMISE); - b->last = (u_char *) ngx_http_v3_encode_varlen_int(b->last, n); - - hl = ngx_alloc_chain_link(r->pool); - if (hl == NULL) { - return NULL; - } - - hl->buf = b; - hl->next = cl; - - return hl; -} -- cgit v1.2.3 From c3714a8089f50e15966db73c227c4eea6d9f8e77 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Wed, 16 Dec 2020 12:47:38 +0000 Subject: HTTP/3: staticize ngx_http_v3_methods. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 2b50133f1..4597bc180 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -14,7 +14,7 @@ static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); -struct { +static const struct { ngx_str_t name; ngx_uint_t method; } ngx_http_v3_methods[] = { -- cgit v1.2.3 From 9e489d208fff35c490b43980a064c38cc8dc4f2c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 22 Jan 2021 16:34:06 +0300 Subject: HTTP/3: refactored request parser. The change reduces diff to the default branch for src/http/ngx_http_request.c and src/http/ngx_http_parse.c. --- src/http/v3/ngx_http_v3_request.c | 524 +++++++++++++++++++++++++------------- 1 file changed, 345 insertions(+), 179 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 4597bc180..09c1ec335 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,8 +10,13 @@ #include +static void ngx_http_v3_process_request(ngx_event_t *rev); +static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r); static const struct { @@ -37,230 +42,256 @@ static const struct { }; -ngx_int_t -ngx_http_v3_parse_request(ngx_http_request_t *r, ngx_buf_t *b) +void +ngx_http_v3_init(ngx_connection_t *c) { - size_t len; - u_char *p; - ngx_int_t rc, n; - ngx_str_t *name, *value; - ngx_connection_t *c; - ngx_http_v3_parse_headers_t *st; - - c = r->connection; - st = r->h3_parse; - - if (st == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 parse header"); - - st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_headers_t)); - if (st == NULL) { - goto failed; - } - - r->h3_parse = st; - r->parse_start = b->pos; - r->state = 1; + size_t size; + ngx_buf_t *b; + ngx_event_t *rev; + ngx_http_request_t *r; + ngx_http_connection_t *hc; + ngx_http_core_srv_conf_t *cscf; + + if (ngx_http_v3_init_session(c) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "internal error"); + ngx_http_close_connection(c); + return; } - while (b->pos < b->last) { - rc = ngx_http_v3_parse_headers(c, st, *b->pos); - - if (rc > 0) { - ngx_http_v3_finalize_connection(c, rc, - "could not parse request headers"); - goto failed; - } - - if (rc == NGX_ERROR) { - goto failed; - } + if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + ngx_http_v3_init_uni_stream(c); + return; + } - if (rc == NGX_BUSY) { - return NGX_BUSY; - } + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream"); - b->pos++; + hc = c->data; - if (rc == NGX_AGAIN) { - continue; - } + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); - name = &st->header_rep.header.name; - value = &st->header_rep.header.value; + size = cscf->client_header_buffer_size; - n = ngx_http_v3_process_pseudo_header(r, name, value); + b = c->buffer; - if (n == NGX_ERROR) { - goto failed; + if (b == NULL) { + b = ngx_create_temp_buf(c->pool, size); + if (b == NULL) { + ngx_http_close_connection(c); + return; } - if (n == NGX_OK && rc == NGX_OK) { - continue; - } + c->buffer = b; - len = r->method_name.len + 1 - + (r->uri_end - r->uri_start) + 1 - + sizeof("HTTP/3.0") - 1; + } else if (b->start == NULL) { - p = ngx_pnalloc(c->pool, len); - if (p == NULL) { - goto failed; + b->start = ngx_palloc(c->pool, size); + if (b->start == NULL) { + ngx_http_close_connection(c); + return; } - r->request_start = p; - - p = ngx_cpymem(p, r->method_name.data, r->method_name.len); - r->method_end = p - 1; - *p++ = ' '; - p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); - *p++ = ' '; - r->http_protocol.data = p; - p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1); + b->pos = b->start; + b->last = b->start; + b->end = b->last + size; + } - r->request_end = p; - r->state = 0; + c->log->action = "reading client request"; - return NGX_OK; + r = ngx_http_create_request(c); + if (r == NULL) { + ngx_http_close_connection(c); + return; } - return NGX_AGAIN; + r->http_version = NGX_HTTP_VERSION_30; -failed: + c->data = r; - return NGX_HTTP_PARSE_INVALID_REQUEST; + rev = c->read; + rev->handler = ngx_http_v3_process_request; + + ngx_http_v3_process_request(rev); } -ngx_int_t -ngx_http_v3_parse_header(ngx_http_request_t *r, ngx_buf_t *b, - ngx_uint_t allow_underscores) +static void +ngx_http_v3_process_request(ngx_event_t *rev) { - u_char ch; + ssize_t n; + ngx_buf_t *b; ngx_int_t rc; - ngx_str_t *name, *value; - ngx_uint_t hash, i, n; ngx_connection_t *c; + ngx_http_request_t *r; + ngx_http_core_srv_conf_t *cscf; ngx_http_v3_parse_headers_t *st; - enum { - sw_start = 0, - sw_done, - sw_next, - sw_header - }; - c = r->connection; - st = r->h3_parse; + c = rev->data; + r = c->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http3 process request"); - switch (r->state) { + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + ngx_http_close_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } - case sw_start: - r->parse_start = b->pos; + st = r->h3_parse; - if (st->state) { - r->state = sw_next; - goto done; + if (st == NULL) { + st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_headers_t)); + if (st == NULL) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return; } - name = &st->header_rep.header.name; + r->h3_parse = st; + } - if (name->len && name->data[0] != ':') { - r->state = sw_done; - goto done; - } + b = r->header_in; - /* fall through */ + for ( ;; ) { - case sw_done: - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse header done"); - return NGX_HTTP_PARSE_HEADER_DONE; + if (b->pos == b->last) { - case sw_next: - r->parse_start = b->pos; - r->invalid_header = 0; - break; + if (!rev->ready) { + break; + } - case sw_header: - break; - } + n = c->recv(c, b->start, b->end - b->start); + + if (n == NGX_AGAIN) { + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(r, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + break; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client prematurely closed connection"); + } - while (b->pos < b->last) { - rc = ngx_http_v3_parse_headers(c, st, *b->pos++); + if (n == 0 || n == NGX_ERROR) { + c->error = 1; + c->log->action = "reading client request"; + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; + } + + b->pos = b->start; + b->last = b->start + n; + } + + rc = ngx_http_v3_parse_headers(c, st, *b->pos); if (rc > 0) { ngx_http_v3_finalize_connection(c, rc, "could not parse request headers"); - return NGX_HTTP_PARSE_INVALID_HEADER; + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + break; } if (rc == NGX_ERROR) { - return NGX_HTTP_PARSE_INVALID_HEADER; + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "internal error"); + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + break; } - if (rc == NGX_DONE) { - r->state = sw_done; - goto done; + if (rc == NGX_BUSY) { + if (rev->error) { + ngx_http_close_request(r, NGX_HTTP_CLOSE); + break; + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + } + + break; } - if (rc == NGX_OK) { - r->state = sw_next; - goto done; + b->pos++; + r->request_length++; + + if (rc == NGX_AGAIN) { + continue; } - } - r->state = sw_header; - return NGX_AGAIN; + /* rc == NGX_OK || rc == NGX_DONE */ -done: + if (ngx_http_v3_process_header(r, &st->header_rep.header.name, + &st->header_rep.header.value) + != NGX_OK) + { + break; + } - name = &st->header_rep.header.name; - value = &st->header_rep.header.value; + if (rc == NGX_DONE) { + if (ngx_http_v3_process_request_header(r) != NGX_OK) { + break; + } - r->header_name_start = name->data; - r->header_name_end = name->data + name->len; - r->header_start = value->data; - r->header_end = value->data + value->len; + ngx_http_process_request(r); + break; + } + } - hash = 0; - i = 0; + ngx_http_run_posted_requests(c); - for (n = 0; n < name->len; n++) { - ch = name->data[n]; + return; +} - if (ch >= 'A' && ch <= 'Z') { - /* - * A request or response containing uppercase - * header field names MUST be treated as malformed - */ - return NGX_HTTP_PARSE_INVALID_HEADER; - } - if (ch == '\0') { - return NGX_HTTP_PARSE_INVALID_HEADER; - } +static ngx_int_t +ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; - if (ch == '_' && !allow_underscores) { - r->invalid_header = 1; - continue; - } + if (name->len && name->data[0] == ':') { + return ngx_http_v3_process_pseudo_header(r, name, value); + } - if ((ch < 'a' || ch > 'z') - && (ch < '0' || ch > '9') - && ch != '-' && ch != '_') - { - r->invalid_header = 1; - continue; - } + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { + return NGX_ERROR; + } - hash = ngx_hash(hash, ch); - r->lowcase_header[i++] = ch; - i &= (NGX_HTTP_LC_HEADER_LEN - 1); + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; } - r->header_hash = hash; - r->lowcase_index = i; + h->key = *name; + h->value = *value; + h->lowcase_key = h->key.data; + h->hash = ngx_hash_key(h->key.data, h->key.len); + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 header: \"%V: %V\"", name, value); return NGX_OK; } @@ -269,75 +300,210 @@ static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) { - ngx_uint_t i; - ngx_connection_t *c; + ngx_uint_t i; - if (name->len == 0 || name->data[0] != ':') { - return NGX_DONE; + if (r->request_line.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent out of order pseudo-headers"); + goto failed; } - c = r->connection; - if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { + r->method_name = *value; for (i = 0; i < sizeof(ngx_http_v3_methods) / sizeof(ngx_http_v3_methods[0]); i++) { if (value->len == ngx_http_v3_methods[i].name.len - && ngx_strncmp(value->data, ngx_http_v3_methods[i].name.data, - value->len) == 0) + && ngx_strncmp(value->data, + ngx_http_v3_methods[i].name.data, value->len) + == 0) { r->method = ngx_http_v3_methods[i].method; break; } } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 method \"%V\" %ui", value, r->method); return NGX_OK; } if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { + r->uri_start = value->data; r->uri_end = value->data + value->len; if (ngx_http_parse_uri(r) != NGX_OK) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client sent invalid :path header: \"%V\"", value); - return NGX_ERROR; + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid \":path\" header: \"%V\"", + value); + goto failed; } - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 path \"%V\"", value); - return NGX_OK; } if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { - r->schema_start = value->data; - r->schema_end = value->data + value->len; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 schema \"%V\"", value); + r->schema = *value; + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 schema \"%V\"", value); return NGX_OK; } if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { + r->host_start = value->data; r->host_end = value->data + value->len; - ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 authority \"%V\"", value); + return NGX_OK; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent unknown pseudo-header \"%V\"", name); + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + +static ngx_int_t +ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) +{ + size_t len; + u_char *p; + ngx_int_t rc; + ngx_str_t host; + + if (r->request_line.len) { return NGX_OK; } - ngx_log_debug2(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 unknown pseudo header \"%V\" \"%V\"", name, value); + len = r->method_name.len + 1 + + (r->uri_end - r->uri_start) + 1 + + sizeof("HTTP/3.0") - 1; + + p = ngx_pnalloc(r->pool, len); + if (p == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + r->request_line.data = p; + + p = ngx_cpymem(p, r->method_name.data, r->method_name.len); + *p++ = ' '; + p = ngx_cpymem(p, r->uri_start, r->uri_end - r->uri_start); + *p++ = ' '; + p = ngx_cpymem(p, "HTTP/3.0", sizeof("HTTP/3.0") - 1); + + r->request_line.len = p - r->request_line.data; + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request line: \"%V\"", &r->request_line); + + ngx_str_set(&r->http_protocol, "HTTP/3.0"); + + if (ngx_http_process_request_uri(r) != NGX_OK) { + return NGX_ERROR; + } + + if (r->host_end) { + + host.len = r->host_end - r->host_start; + host.data = r->host_start; + + rc = ngx_http_validate_host(&host, r->pool, 0); + + if (rc == NGX_DECLINED) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid host in request line"); + goto failed; + } + + if (rc == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (ngx_http_set_virtual_server(r, &host) == NGX_ERROR) { + return NGX_ERROR; + } + + r->headers_in.server = host; + } + + if (ngx_list_init(&r->headers_in.headers, r->pool, 20, + sizeof(ngx_table_elt_t)) + != NGX_OK) + { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } return NGX_OK; + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; +} + + +static ngx_int_t +ngx_http_v3_process_request_header(ngx_http_request_t *r) +{ + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { + return NGX_ERROR; + } + + if (r->headers_in.server.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent neither \":authority\" nor \"Host\" header"); + goto failed; + } + + if (r->headers_in.host) { + if (r->headers_in.host->value.len != r->headers_in.server.len + || ngx_memcmp(r->headers_in.host->value.data, + r->headers_in.server.data, + r->headers_in.server.len) + != 0) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent \":authority\" and \"Host\" headers " + "with different values"); + goto failed; + } + } + + if (r->headers_in.content_length) { + r->headers_in.content_length_n = + ngx_atoof(r->headers_in.content_length->value.data, + r->headers_in.content_length->value.len); + + if (r->headers_in.content_length_n == NGX_ERROR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid \"Content-Length\" header"); + goto failed; + } + } + + return NGX_OK; + +failed: + + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; } -- cgit v1.2.3 From 4e312daa7ec04e52cdefcbc8749ef2f6d366064b Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 22 Jan 2021 15:57:41 +0300 Subject: HTTP/3: client pseudo-headers restrictions. - :method, :path and :scheme are expected exactly once and not empty - :method and :scheme character validation is added - :authority cannot appear more than once --- src/http/v3/ngx_http_v3_request.c | 92 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 09c1ec335..59a8889bf 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -300,6 +300,7 @@ static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) { + u_char ch, c; ngx_uint_t i; if (r->request_line.len) { @@ -310,6 +311,18 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, if (name->len == 7 && ngx_strncmp(name->data, ":method", 7) == 0) { + if (r->method_name.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":method\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":method\" header"); + goto failed; + } + r->method_name = *value; for (i = 0; i < sizeof(ngx_http_v3_methods) @@ -325,6 +338,16 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, } } + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + if ((ch < 'A' || ch > 'Z') && ch != '_' && ch != '-') { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid method: \"%V\"", value); + goto failed; + } + } + ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 method \"%V\" %ui", value, r->method); return NGX_OK; @@ -332,6 +355,18 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, if (name->len == 5 && ngx_strncmp(name->data, ":path", 5) == 0) { + if (r->uri_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":path\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":path\" header"); + goto failed; + } + r->uri_start = value->data; r->uri_end = value->data + value->len; @@ -349,6 +384,39 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, if (name->len == 7 && ngx_strncmp(name->data, ":scheme", 7) == 0) { + if (r->schema.len) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":scheme\" header"); + goto failed; + } + + if (value->len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent empty \":scheme\" header"); + goto failed; + } + + for (i = 0; i < value->len; i++) { + ch = value->data[i]; + + c = (u_char) (ch | 0x20); + if (c >= 'a' && c <= 'z') { + continue; + } + + if (((ch >= '0' && ch <= '9') + || ch == '+' || ch == '-' || ch == '.') + && i > 0) + { + continue; + } + + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid \":scheme\" header: \"%V\"", + value); + goto failed; + } + r->schema = *value; ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -358,6 +426,12 @@ ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, if (name->len == 10 && ngx_strncmp(name->data, ":authority", 10) == 0) { + if (r->host_start) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent duplicate \":authority\" header"); + goto failed; + } + r->host_start = value->data; r->host_end = value->data + value->len; @@ -388,6 +462,24 @@ ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r) return NGX_OK; } + if (r->method_name.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":method\" header"); + goto failed; + } + + if (r->schema.len == 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":scheme\" header"); + goto failed; + } + + if (r->uri_start == NULL) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent no \":path\" header"); + goto failed; + } + len = r->method_name.len + 1 + (r->uri_end - r->uri_start) + 1 + sizeof("HTTP/3.0") - 1; -- cgit v1.2.3 From 7bac596afb31344cf40c93d8ae1ce87d2b6c76c1 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 18 Jan 2021 13:43:36 +0300 Subject: HTTP/3: client header validation. A header with the name containing null, CR, LF, colon or uppercase characters, is now considered an error. A header with the value containing null, CR or LF, is also considered an error. Also, header is considered invalid unless its name only contains lowercase characters, digits, minus and optionally underscore. Such header can be optionally ignored. --- src/http/v3/ngx_http_v3_request.c | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 59a8889bf..fb1626718 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -13,6 +13,8 @@ static void ngx_http_v3_process_request(ngx_event_t *rev); static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); +static ngx_int_t ngx_http_v3_validate_header(ngx_http_request_t *r, + ngx_str_t *name, ngx_str_t *value); static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); @@ -260,8 +262,25 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, { ngx_table_elt_t *h; ngx_http_header_t *hh; + ngx_http_core_srv_conf_t *cscf; ngx_http_core_main_conf_t *cmcf; + if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) { + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); + return NGX_ERROR; + } + + if (r->invalid_header) { + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + if (cscf->ignore_invalid_headers) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header: \"%V\"", name); + + return NGX_OK; + } + } + if (name->len && name->data[0] == ':') { return ngx_http_v3_process_pseudo_header(r, name, value); } @@ -296,6 +315,57 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, } +static ngx_int_t +ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, + ngx_str_t *value) +{ + u_char ch; + ngx_uint_t i; + ngx_http_core_srv_conf_t *cscf; + + r->invalid_header = 0; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + for (i = (name->data[0] == ':'); i != name->len; i++) { + ch = name->data[i]; + + if ((ch >= 'a' && ch <= 'z') + || (ch == '-') + || (ch >= '0' && ch <= '9') + || (ch == '_' && cscf->underscores_in_headers)) + { + continue; + } + + if (ch == '\0' || ch == LF || ch == CR || ch == ':' + || (ch >= 'A' && ch <= 'Z')) + { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent invalid header name: \"%V\"", name); + + return NGX_ERROR; + } + + r->invalid_header = 1; + } + + for (i = 0; i != value->len; i++) { + ch = value->data[i]; + + if (ch == '\0' || ch == LF || ch == CR) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent header \"%V\" with " + "invalid value: \"%V\"", name, value); + + return NGX_ERROR; + } + } + + return NGX_OK; +} + + static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) -- cgit v1.2.3 From cd6253430051a823dc31b756e93aeecb5f674af3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 29 Jan 2021 19:42:47 +0300 Subject: HTTP/3: call ngx_handle_read_event() from client header handler. This function should be called at the end of an event handler to prepare the event for the next handler call. Particularly, the "active" flag is set or cleared depending on data availability. With this call missing in one code path, read handler was not called again after handling the initial part of the client request, if the request was too big to fit into a single STREAM frame. Now ngx_handle_read_event() is called in this code path. Also, read timer is restarted. --- src/http/v3/ngx_http_v3_request.c | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index fb1626718..0b7954137 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -158,11 +158,12 @@ ngx_http_v3_process_request(ngx_event_t *rev) if (b->pos == b->last) { - if (!rev->ready) { - break; - } + if (rev->ready) { + n = c->recv(c, b->start, b->end - b->start); - n = c->recv(c, b->start, b->end - b->start); + } else { + n = NGX_AGAIN; + } if (n == NGX_AGAIN) { if (!rev->timer_set) { -- cgit v1.2.3 From 6f3c821d1f28c433f778fcc843bb764e45194f5c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 25 Jan 2021 16:16:47 +0300 Subject: HTTP/3: refactored request body parser. The change reduces diff to the default branch for src/http/ngx_http_request_body.c. Also, client Content-Length, if present, is now checked against the real body size sent by client. --- src/http/v3/ngx_http_v3_request.c | 503 ++++++++++++++++++++++++++++++++++---- 1 file changed, 459 insertions(+), 44 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 0b7954137..1c17efadb 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -19,6 +19,10 @@ static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r); +static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r, + ngx_chain_t *in); static const struct { @@ -625,12 +629,18 @@ failed: static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r) { + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + + c = r->connection; + if (ngx_http_v3_init_pseudo_headers(r) != NGX_OK) { return NGX_ERROR; } if (r->headers_in.server.len == 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent neither \":authority\" nor \"Host\" header"); goto failed; } @@ -642,7 +652,7 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) r->headers_in.server.len) != 0) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent \":authority\" and \"Host\" headers " "with different values"); goto failed; @@ -655,10 +665,32 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) r->headers_in.content_length->value.len); if (r->headers_in.content_length_n == NGX_ERROR) { - ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent invalid \"Content-Length\" header"); goto failed; } + + } else { + b = r->header_in; + n = b->last - b->pos; + + if (n == 0) { + n = c->recv(c, b->start, b->end - b->start); + + if (n == NGX_ERROR) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (n > 0) { + b->pos = b->start; + b->last = b->start + n; + } + } + + if (n != 0) { + r->headers_in.chunked = 1; + } } return NGX_OK; @@ -671,74 +703,457 @@ failed: ngx_int_t -ngx_http_v3_parse_request_body(ngx_http_request_t *r, ngx_buf_t *b, - ngx_http_chunked_t *ctx) +ngx_http_v3_read_request_body(ngx_http_request_t *r) +{ + size_t preread; + ngx_int_t rc; + ngx_chain_t *cl, out; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + + rb = r->request_body; + + preread = r->header_in->last - r->header_in->pos; + + if (preread) { + + /* there is the pre-read part of the request body */ + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 client request body preread %uz", preread); + + out.buf = r->header_in; + out.next = NULL; + cl = &out; + + } else { + cl = NULL; + } + + rc = ngx_http_v3_request_body_filter(r, cl); + if (rc != NGX_OK) { + return rc; + } + + if (rb->rest == 0) { + /* the whole request body was pre-read */ + r->request_body_no_buffering = 0; + rb->post_handler(r); + return NGX_OK; + } + + if (rb->rest < 0) { + ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0, + "negative request body rest"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + + rb->buf = ngx_create_temp_buf(r->pool, clcf->client_body_buffer_size); + if (rb->buf == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + r->read_event_handler = ngx_http_v3_read_client_request_body_handler; + r->write_event_handler = ngx_http_request_empty_handler; + + return ngx_http_v3_do_read_client_request_body(r); +} + + +static void +ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r) +{ + ngx_int_t rc; + + if (r->connection->read->timedout) { + r->connection->timedout = 1; + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_TIME_OUT); + return; + } + + rc = ngx_http_v3_do_read_client_request_body(r); + + if (rc >= NGX_HTTP_SPECIAL_RESPONSE) { + ngx_http_finalize_request(r, rc); + } +} + + +ngx_int_t +ngx_http_v3_read_unbuffered_request_body(ngx_http_request_t *r) { + ngx_int_t rc; + + if (r->connection->read->timedout) { + r->connection->timedout = 1; + return NGX_HTTP_REQUEST_TIME_OUT; + } + + rc = ngx_http_v3_do_read_client_request_body(r); + + if (rc == NGX_OK) { + r->reading_body = 0; + } + + return rc; +} + + +static ngx_int_t +ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) +{ + off_t rest; + size_t size; + ssize_t n; ngx_int_t rc; + ngx_chain_t out; ngx_connection_t *c; - ngx_http_v3_parse_data_t *st; - enum { - sw_start = 0, - sw_skip - }; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; c = r->connection; - st = ctx->h3_parse; + rb = r->request_body; - if (st == NULL) { - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, - "http3 parse request body"); + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 read client request body"); + + for ( ;; ) { + for ( ;; ) { + if (rb->buf->last == rb->buf->end) { + + /* update chains */ + + rc = ngx_http_v3_request_body_filter(r, NULL); + + if (rc != NGX_OK) { + return rc; + } + + if (rb->busy != NULL) { + if (r->request_body_no_buffering) { + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + + ngx_log_error(NGX_LOG_ALERT, c->log, 0, + "busy buffers after request body flush"); + + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + rb->buf->pos = rb->buf->start; + rb->buf->last = rb->buf->start; + } + + size = rb->buf->end - rb->buf->last; + rest = rb->rest - (rb->buf->last - rb->buf->pos); + + if ((off_t) size > rest) { + size = (size_t) rest; + } + + n = c->recv(c, rb->buf->last, size); + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client request body recv %z", n); + + if (n == NGX_AGAIN) { + break; + } - st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_data_t)); + if (n == 0) { + rb->buf->last_buf = 1; + } + + if (n == NGX_ERROR) { + c->error = 1; + return NGX_HTTP_BAD_REQUEST; + } + + rb->buf->last += n; + + /* pass buffer to request body filter chain */ + + out.buf = rb->buf; + out.next = NULL; + + rc = ngx_http_v3_request_body_filter(r, &out); + + if (rc != NGX_OK) { + return rc; + } + + if (rb->rest == 0) { + break; + } + + if (rb->buf->last < rb->buf->end) { + break; + } + } + + ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, + "http3 client request body rest %O", rb->rest); + + if (rb->rest == 0) { + break; + } + + if (!c->read->ready) { + + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); + ngx_add_timer(c->read, clcf->client_body_timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + } + + if (c->read->timer_set) { + ngx_del_timer(c->read); + } + + if (!r->request_body_no_buffering) { + r->read_event_handler = ngx_http_block_reading; + rb->post_handler(r); + } + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) +{ + off_t max; + size_t size; + ngx_int_t rc; + ngx_buf_t *b; + ngx_uint_t last; + ngx_chain_t *cl, *out, *tl, **ll; + ngx_http_request_body_t *rb; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + ngx_http_v3_parse_data_t *st; + + rb = r->request_body; + st = r->h3_parse; + + if (rb->rest == -1) { + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, + "http3 request body filter"); + + st = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_data_t)); if (st == NULL) { - goto failed; + return NGX_HTTP_INTERNAL_SERVER_ERROR; } - ctx->h3_parse = st; + r->h3_parse = st; + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + rb->rest = cscf->large_client_header_buffers.size; } - while (b->pos < b->last && ctx->size == 0) { + clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); - rc = ngx_http_v3_parse_data(c, st, *b->pos++); + max = r->headers_in.content_length_n; - if (rc > 0) { - ngx_http_v3_finalize_connection(c, rc, - "could not parse request body"); - goto failed; + if (max == -1 && clcf->client_max_body_size) { + max = clcf->client_max_body_size; + } + + out = NULL; + ll = &out; + last = 0; + + for (cl = in; cl; cl = cl->next) { + + ngx_log_debug7(NGX_LOG_DEBUG_EVENT, r->connection->log, 0, + "http3 body buf " + "t:%d f:%d %p, pos %p, size: %z file: %O, size: %O", + 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); + + if (cl->buf->last_buf) { + last = 1; } - if (rc == NGX_ERROR) { - goto failed; + b = NULL; + + while (cl->buf->pos < cl->buf->last) { + + if (st->length == 0) { + r->request_length++; + + rc = ngx_http_v3_parse_data(r->connection, st, *cl->buf->pos++); + + if (rc == NGX_AGAIN) { + continue; + } + + if (rc == NGX_DONE) { + last = 1; + goto done; + } + + if (rc > 0) { + ngx_http_v3_finalize_connection(r->connection, rc, + "client sent invalid body"); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid body"); + return NGX_HTTP_BAD_REQUEST; + } + + if (rc == NGX_ERROR) { + ngx_http_v3_finalize_connection(r->connection, + NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "internal error"); + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + /* rc == NGX_OK */ + } + + if (max != -1 && (uint64_t) (max - rb->received) < st->length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send too large " + "body: %O+%uL bytes", + rb->received, st->length); + + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } + + if (b + && st->length <= 128 + && (uint64_t) (cl->buf->last - cl->buf->pos) >= st->length) + { + rb->received += st->length; + r->request_length += st->length; + + if (st->length < 8) { + + while (st->length) { + *b->last++ = *cl->buf->pos++; + st->length--; + } + + } else { + ngx_memmove(b->last, cl->buf->pos, st->length); + b->last += st->length; + cl->buf->pos += st->length; + st->length = 0; + } + + continue; + } + + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->temporary = 1; + b->tag = (ngx_buf_tag_t) &ngx_http_read_client_request_body; + b->start = cl->buf->pos; + b->pos = cl->buf->pos; + b->last = cl->buf->last; + b->end = cl->buf->end; + b->flush = r->request_body_no_buffering; + + *ll = tl; + ll = &tl->next; + + size = cl->buf->last - cl->buf->pos; + + if (size > st->length) { + cl->buf->pos += (size_t) st->length; + rb->received += st->length; + r->request_length += st->length; + st->length = 0; + + } else { + st->length -= size; + rb->received += size; + r->request_length += size; + cl->buf->pos = cl->buf->last; + } + + b->last = cl->buf->pos; } + } - if (rc == NGX_AGAIN) { - ctx->state = sw_skip; - continue; +done: + + if (last) { + + if (st->length > 0) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client prematurely closed stream"); + r->connection->error = 1; + return NGX_HTTP_BAD_REQUEST; } - if (rc == NGX_DONE) { - return NGX_DONE; + if (r->headers_in.content_length_n == -1) { + r->headers_in.content_length_n = rb->received; + + } else if (r->headers_in.content_length_n != rb->received) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent less body data than expected: " + "%O out of %O bytes of request body received", + rb->received, r->headers_in.content_length_n); + return NGX_HTTP_BAD_REQUEST; } - /* rc == NGX_OK */ + rb->rest = 0; - ctx->size = st->length; - ctx->state = sw_start; - } + tl = ngx_chain_get_free_buf(r->pool, &rb->free); + if (tl == NULL) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } - if (ctx->state == sw_skip) { - ctx->length = 1; - return NGX_AGAIN; - } + b = tl->buf; + + ngx_memzero(b, sizeof(ngx_buf_t)); + + b->last_buf = 1; + + *ll = tl; + ll = &tl->next; + + } else { - if (b->pos == b->last) { - ctx->length = ctx->size; - return NGX_AGAIN; + /* set rb->rest, amount of data we want to see next time */ + + cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); + + rb->rest = (off_t) cscf->large_client_header_buffers.size; } - return NGX_OK; + rc = ngx_http_top_request_body_filter(r, out); -failed: + ngx_chain_update_chains(r->pool, &rb->free, &rb->busy, &out, + (ngx_buf_tag_t) &ngx_http_read_client_request_body); - return NGX_ERROR; + return rc; } -- cgit v1.2.3 From a373d2851b33191e4f82cdec911914b04c4a4f23 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 1 Feb 2021 18:48:18 +0300 Subject: HTTP/3: fixed format specifier. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 1c17efadb..df58f383a 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1034,7 +1034,7 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) if (max != -1 && (uint64_t) (max - rb->received) < st->length) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "client intended to send too large " - "body: %O+%uL bytes", + "body: %O+%ui bytes", rb->received, st->length); return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; -- cgit v1.2.3 From ffb099bf52e70c0cbdb1ed5555645f12ec6b2322 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 17 Feb 2021 15:56:34 +0300 Subject: HTTP/3: introduced ngx_http_v3_parse_t structure. The structure is used to parse an HTTP/3 request. An object of this type is added to ngx_http_request_t instead of h3_parse generic pointer. Also, the new field is located outside of the request ephemeral zone to keep it safe after request headers are parsed. --- src/http/v3/ngx_http_v3_request.c | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index df58f383a..ef3053689 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -112,6 +112,12 @@ ngx_http_v3_init(ngx_connection_t *c) r->http_version = NGX_HTTP_VERSION_30; + r->v3_parse = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_t)); + if (r->v3_parse == NULL) { + ngx_http_close_connection(c); + return; + } + c->data = r; rev = c->read; @@ -144,17 +150,7 @@ ngx_http_v3_process_request(ngx_event_t *rev) return; } - st = r->h3_parse; - - if (st == NULL) { - st = ngx_pcalloc(c->pool, sizeof(ngx_http_v3_parse_headers_t)); - if (st == NULL) { - ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); - return; - } - - r->h3_parse = st; - } + st = &r->v3_parse->headers; b = r->header_in; @@ -949,20 +945,13 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ngx_http_v3_parse_data_t *st; rb = r->request_body; - st = r->h3_parse; + st = &r->v3_parse->body; if (rb->rest == -1) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, "http3 request body filter"); - st = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_data_t)); - if (st == NULL) { - return NGX_HTTP_INTERNAL_SERVER_ERROR; - } - - r->h3_parse = st; - cscf = ngx_http_get_module_srv_conf(r, ngx_http_core_module); rb->rest = cscf->large_client_header_buffers.size; -- cgit v1.2.3 From e0425791d484b8e1e77cf39f6ca4da33b5c6e3a3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 17 Feb 2021 11:58:32 +0300 Subject: HTTP/3: limited client header size. The limit is the size of all large client header buffers. Client header size is the total size of all client header names and values. --- src/http/v3/ngx_http_v3_request.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index ef3053689..689d9fc61 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -118,6 +118,9 @@ ngx_http_v3_init(ngx_connection_t *c) return; } + r->v3_parse->header_limit = cscf->large_client_header_buffers.size + * cscf->large_client_header_buffers.num; + c->data = r; rev = c->read; @@ -261,11 +264,23 @@ static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value) { + size_t len; ngx_table_elt_t *h; ngx_http_header_t *hh; ngx_http_core_srv_conf_t *cscf; ngx_http_core_main_conf_t *cmcf; + len = name->len + value->len; + + if (len > r->v3_parse->header_limit) { + ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, + "client sent too large header"); + ngx_http_finalize_request(r, NGX_HTTP_REQUEST_HEADER_TOO_LARGE); + return NGX_ERROR; + } + + r->v3_parse->header_limit -= len; + if (ngx_http_v3_validate_header(r, name, value) != NGX_OK) { ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); return NGX_ERROR; -- cgit v1.2.3 From 190b5d961c0c9b0942dd1a2d8cd609416d0d5114 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 15 Mar 2021 19:26:04 +0300 Subject: HTTP/3: send GOAWAY when last request is accepted. The last request in connection is determined according to the keepalive_requests directive. Requests beyond keepalive_requests are rejected. --- src/http/v3/ngx_http_v3_request.c | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 689d9fc61..0c055ba0e 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -52,10 +52,12 @@ void ngx_http_v3_init(ngx_connection_t *c) { size_t size; + uint64_t n; ngx_buf_t *b; ngx_event_t *rev; ngx_http_request_t *r; ngx_http_connection_t *hc; + ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; if (ngx_http_v3_init_session(c) != NGX_OK) { @@ -74,6 +76,25 @@ ngx_http_v3_init(ngx_connection_t *c) hc = c->data; + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + + n = c->quic->id >> 2; + + if (n >= clcf->keepalive_requests) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); + ngx_http_close_connection(c); + return; + } + + if (n + 1 == clcf->keepalive_requests) { + if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, + "goaway error"); + ngx_http_close_connection(c); + return; + } + } + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); size = cscf->client_header_buffer_size; -- cgit v1.2.3 From 9533df5b728833dd516f44d18953a3731c29e787 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 15 Mar 2021 16:39:33 +0300 Subject: QUIC: connection shutdown. The function ngx_quic_shutdown_connection() waits until all non-cancelable streams are closed, and then closes the connection. In HTTP/3 cancelable streams are all unidirectional streams except push streams. The function is called from HTTP/3 when client reaches keepalive_requests. --- src/http/v3/ngx_http_v3_request.c | 3 +++ 1 file changed, 3 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 0c055ba0e..d4a5faccf 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -93,6 +93,9 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_http_close_connection(c); return; } + + ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "reached maximum number of requests"); } cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); -- cgit v1.2.3 From f4ab680bcb34d832a1dfd086e3641bf78c0a207c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 30 Mar 2021 16:48:38 +0300 Subject: HTTP/3: keepalive timeout. This timeout limits the time when no client request streams exist. --- src/http/v3/ngx_http_v3_request.c | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index d4a5faccf..b997c29a1 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,6 +10,7 @@ #include +static void ngx_http_v3_cleanup_request(void *data); static void ngx_http_v3_process_request(ngx_event_t *rev); static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); @@ -55,8 +56,10 @@ ngx_http_v3_init(ngx_connection_t *c) uint64_t n; ngx_buf_t *b; ngx_event_t *rev; + ngx_pool_cleanup_t *cln; ngx_http_request_t *r; ngx_http_connection_t *hc; + ngx_http_v3_connection_t *h3c; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; @@ -98,6 +101,22 @@ ngx_http_v3_init(ngx_connection_t *c) "reached maximum number of requests"); } + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_v3_cleanup_request; + cln->data = c; + + h3c = c->quic->parent->data; + h3c->nrequests++; + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); size = cscf->client_header_buffer_size; @@ -154,6 +173,23 @@ ngx_http_v3_init(ngx_connection_t *c) } +static void +ngx_http_v3_cleanup_request(void *data) +{ + ngx_connection_t *c = data; + + ngx_http_core_loc_conf_t *clcf; + ngx_http_v3_connection_t *h3c; + + h3c = c->quic->parent->data; + + if (--h3c->nrequests == 0) { + clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); + ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); + } +} + + static void ngx_http_v3_process_request(ngx_event_t *rev) { -- cgit v1.2.3 From 7d1cf8ffb442727bc8e54630dd565c8139cead67 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 15 Mar 2021 16:25:54 +0300 Subject: HTTP/3: fixed $connection_requests. Previously, the value was always "1". --- src/http/v3/ngx_http_v3_request.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index b997c29a1..4dc673078 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -165,6 +165,7 @@ ngx_http_v3_init(ngx_connection_t *c) * cscf->large_client_header_buffers.num; c->data = r; + c->requests = n + 1; rev = c->read; rev->handler = ngx_http_v3_process_request; -- cgit v1.2.3 From 2fd50ca589d37b4dd66006254d99918f44617cf0 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Fri, 16 Apr 2021 19:42:03 +0300 Subject: HTTP/3: keepalive_time support. --- src/http/v3/ngx_http_v3_request.c | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 4dc673078..c459efef5 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -81,15 +81,22 @@ ngx_http_v3_init(ngx_connection_t *c) clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); - n = c->quic->id >> 2; + h3c = c->quic->parent->data; - if (n >= clcf->keepalive_requests) { + if (h3c->goaway) { ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); ngx_http_close_connection(c); return; } - if (n + 1 == clcf->keepalive_requests) { + n = c->quic->id >> 2; + + if (n + 1 == clcf->keepalive_requests + || ngx_current_msec - c->quic->parent->start_time + > clcf->keepalive_time) + { + h3c->goaway = 1; + if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, "goaway error"); @@ -110,7 +117,6 @@ ngx_http_v3_init(ngx_connection_t *c) cln->handler = ngx_http_v3_cleanup_request; cln->data = c; - h3c = c->quic->parent->data; h3c->nrequests++; if (h3c->keepalive.timer_set) { -- cgit v1.2.3 From 82f8734935ef28fbda4450fd88410b7d1f359c62 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Fri, 30 Apr 2021 19:10:11 +0300 Subject: HTTP/3: ngx_http_v3_get_session() macro. It's used instead of accessing c->quic->parent->data directly. Apart from being simpler, it allows to change the way session is stored in the future by changing the macro. --- src/http/v3/ngx_http_v3_request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index c459efef5..23b827aed 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -81,7 +81,7 @@ ngx_http_v3_init(ngx_connection_t *c) clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); if (h3c->goaway) { ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); @@ -188,7 +188,7 @@ ngx_http_v3_cleanup_request(void *data) ngx_http_core_loc_conf_t *clcf; ngx_http_v3_connection_t *h3c; - h3c = c->quic->parent->data; + h3c = ngx_http_v3_get_session(c); if (--h3c->nrequests == 0) { clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); -- cgit v1.2.3 From 0ea300d35eb066631a21b2042cfe9cd2ea94b15f Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 5 May 2021 12:54:10 +0300 Subject: HTTP/3: renamed ngx_http_v3_connection_t to ngx_http_v3_session_t. --- src/http/v3/ngx_http_v3_request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 23b827aed..9d7ca952d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -59,7 +59,7 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_pool_cleanup_t *cln; ngx_http_request_t *r; ngx_http_connection_t *hc; - ngx_http_v3_connection_t *h3c; + ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; @@ -185,8 +185,8 @@ ngx_http_v3_cleanup_request(void *data) { ngx_connection_t *c = data; + ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; - ngx_http_v3_connection_t *h3c; h3c = ngx_http_v3_get_session(c); -- cgit v1.2.3 From a85084fea109c019d1ad7466ed063afa7961acba Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 1 Jul 2021 15:37:53 +0300 Subject: HTTP/3: quic-qpack term updates. Renamed header -> field per quic-qpack naming convention, in particular: - Header Field -> Field Line - Header Block -> (Encoded) Field Section - Without Name Reference -> With Literal Name - Header Acknowledgement -> Section Acknowledgment --- src/http/v3/ngx_http_v3_request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 9d7ca952d..5fc6e233b 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -304,8 +304,8 @@ ngx_http_v3_process_request(ngx_event_t *rev) /* rc == NGX_OK || rc == NGX_DONE */ - if (ngx_http_v3_process_header(r, &st->header_rep.header.name, - &st->header_rep.header.value) + if (ngx_http_v3_process_header(r, &st->field_rep.field.name, + &st->field_rep.field.value) != NGX_OK) { break; -- cgit v1.2.3 From e1ad576f960ab2b455b4d12869f69cb648feba42 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 29 Jul 2021 16:01:37 +0300 Subject: HTTP/3: close connection on keepalive_requests * 2. After receiving GOAWAY, client is not supposed to create new streams. However, until client reads this frame, we allow it to create new streams, which are gracefully rejected. To prevent client from abusing this algorithm, a new limit is introduced. Upon reaching keepalive_requests * 2, server now closes the entire QUIC connection claiming excessive load. --- src/http/v3/ngx_http_v3_request.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 5fc6e233b..f45a7b95e 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -81,6 +81,15 @@ ngx_http_v3_init(ngx_connection_t *c) clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + n = c->quic->id >> 2; + + if (n >= clcf->keepalive_requests * 2) { + ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_EXCESSIVE_LOAD, + "too many requests per connection"); + ngx_http_close_connection(c); + return; + } + h3c = ngx_http_v3_get_session(c); if (h3c->goaway) { @@ -89,8 +98,6 @@ ngx_http_v3_init(ngx_connection_t *c) return; } - n = c->quic->id >> 2; - if (n + 1 == clcf->keepalive_requests || ngx_current_msec - c->quic->parent->start_time > clcf->keepalive_time) -- cgit v1.2.3 From 6fb9bdad6a811f00388044a89d322fbdfd072606 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 10 Aug 2021 12:35:12 +0300 Subject: HTTP/3: disabled control characters and space in header names. This is a follow up to 41f4bd4c51f1. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index f45a7b95e..1fcbad1de 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -428,7 +428,7 @@ ngx_http_v3_validate_header(ngx_http_request_t *r, ngx_str_t *name, continue; } - if (ch == '\0' || ch == LF || ch == CR || ch == ':' + if (ch <= 0x20 || ch == 0x7f || ch == ':' || (ch >= 'A' && ch <= 'Z')) { ngx_log_error(NGX_LOG_INFO, r->connection->log, 0, -- cgit v1.2.3 From 2ff0af368d08ade41cb164eb54f621361c9f1bec Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 24 Aug 2021 13:03:48 +0300 Subject: HTTP/3: fixed dead store assignment. Found by Clang Static Analyzer. --- src/http/v3/ngx_http_v3_request.c | 1 - 1 file changed, 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 1fcbad1de..0bd585317 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1217,7 +1217,6 @@ done: b->last_buf = 1; *ll = tl; - ll = &tl->next; } else { -- cgit v1.2.3 From 68d4325de08053f4cb0db590dc72ef9494c33bd6 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 8 Jul 2021 21:52:47 +0300 Subject: HTTP/3: bulk parse functions. Previously HTTP/3 streams were parsed by one character. Now all parse functions receive buffers. This should optimize parsing time and CPU load. --- src/http/v3/ngx_http_v3_request.c | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 0bd585317..fb9ea8ff1 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -207,6 +207,7 @@ ngx_http_v3_cleanup_request(void *data) static void ngx_http_v3_process_request(ngx_event_t *rev) { + u_char *p; ssize_t n; ngx_buf_t *b; ngx_int_t rc; @@ -273,7 +274,9 @@ ngx_http_v3_process_request(ngx_event_t *rev) b->last = b->start + n; } - rc = ngx_http_v3_parse_headers(c, st, *b->pos); + p = b->pos; + + rc = ngx_http_v3_parse_headers(c, st, b); if (rc > 0) { ngx_http_v3_finalize_connection(c, rc, @@ -302,8 +305,7 @@ ngx_http_v3_process_request(ngx_event_t *rev) break; } - b->pos++; - r->request_length++; + r->request_length += b->pos - p; if (rc == NGX_AGAIN) { continue; @@ -1024,6 +1026,7 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { off_t max; size_t size; + u_char *p; ngx_int_t rc; ngx_buf_t *b; ngx_uint_t last; @@ -1078,9 +1081,11 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) while (cl->buf->pos < cl->buf->last) { if (st->length == 0) { - r->request_length++; + p = cl->buf->pos; + + rc = ngx_http_v3_parse_data(r->connection, st, cl->buf); - rc = ngx_http_v3_parse_data(r->connection, st, *cl->buf->pos++); + r->request_length += cl->buf->pos - p; if (rc == NGX_AGAIN) { continue; -- cgit v1.2.3 From 590996466ca568dbc7de5d40b7062dc254d211c2 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 9 Sep 2021 15:47:29 +0300 Subject: HTTP/3: reading body buffering in filters. This change follows similar changes in HTTP/1 and HTTP/2 in 9cf043a5d9ca. --- src/http/v3/ngx_http_v3_request.c | 38 +++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index fb9ea8ff1..f11c32da9 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -822,7 +822,7 @@ ngx_http_v3_read_request_body(ngx_http_request_t *r) return rc; } - if (rb->rest == 0) { + if (rb->rest == 0 && rb->last_saved) { /* the whole request body was pre-read */ r->request_body_no_buffering = 0; rb->post_handler(r); @@ -895,6 +895,7 @@ ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) size_t size; ssize_t n; ngx_int_t rc; + ngx_uint_t flush; ngx_chain_t out; ngx_connection_t *c; ngx_http_request_body_t *rb; @@ -902,12 +903,17 @@ ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) c = r->connection; rb = r->request_body; + flush = 1; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 read client request body"); for ( ;; ) { for ( ;; ) { + if (rb->rest == 0) { + break; + } + if (rb->buf->last == rb->buf->end) { /* update chains */ @@ -931,12 +937,25 @@ ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) return NGX_AGAIN; } + if (rb->filter_need_buffering) { + clcf = ngx_http_get_module_loc_conf(r, + ngx_http_core_module); + ngx_add_timer(c->read, clcf->client_body_timeout); + + if (ngx_handle_read_event(c->read, 0) != NGX_OK) { + return NGX_HTTP_INTERNAL_SERVER_ERROR; + } + + return NGX_AGAIN; + } + ngx_log_error(NGX_LOG_ALERT, c->log, 0, "busy buffers after request body flush"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } + flush = 0; rb->buf->pos = rb->buf->start; rb->buf->last = rb->buf->start; } @@ -948,6 +967,10 @@ ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) size = (size_t) rest; } + if (size == 0) { + break; + } + n = c->recv(c, rb->buf->last, size); ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, @@ -970,6 +993,7 @@ ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) /* pass buffer to request body filter chain */ + flush = 0; out.buf = rb->buf; out.next = NULL; @@ -991,11 +1015,19 @@ ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r) ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 client request body rest %O", rb->rest); - if (rb->rest == 0) { + if (flush) { + rc = ngx_http_v3_request_body_filter(r, NULL); + + if (rc != NGX_OK) { + return rc; + } + } + + if (rb->rest == 0 && rb->last_saved) { break; } - if (!c->read->ready) { + if (!c->read->ready || rb->rest == 0) { clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module); ngx_add_timer(c->read, clcf->client_body_timeout); -- cgit v1.2.3 From 9d7f2e79176b3fc73c06e8ba1594f287b4536bbe Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 16 Sep 2021 13:13:22 +0300 Subject: HTTP/3: added CONNECT and TRACE methods rejection. It has got lost in e1eb7f4ca9f1, let alone a subsequent update in 63c66b7cc07c. --- src/http/v3/ngx_http_v3_request.c | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index f11c32da9..793a34816 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -45,7 +45,8 @@ static const struct { { ngx_string("LOCK"), NGX_HTTP_LOCK }, { ngx_string("UNLOCK"), NGX_HTTP_UNLOCK }, { ngx_string("PATCH"), NGX_HTTP_PATCH }, - { ngx_string("TRACE"), NGX_HTTP_TRACE } + { ngx_string("TRACE"), NGX_HTTP_TRACE }, + { ngx_string("CONNECT"), NGX_HTTP_CONNECT } }; @@ -780,6 +781,18 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) } } + if (r->method == NGX_HTTP_CONNECT) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent CONNECT method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + + if (r->method == NGX_HTTP_TRACE) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent TRACE method"); + ngx_http_finalize_request(r, NGX_HTTP_NOT_ALLOWED); + return NGX_ERROR; + } + return NGX_OK; failed: -- cgit v1.2.3 From 08dcf62f5b8ee49927dc38bae705b8fa777799e4 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 22 Sep 2021 14:08:21 +0300 Subject: HTTP/3: fixed ngx_stat_active counter. Previously the counter was not incremented for HTTP/3 streams, but still decremented in ngx_http_close_connection(). There are two solutions here, one is to increment the counter for HTTP/3 streams, and the other one is not to decrement the counter for HTTP/3 streams. The latter solution looks inconsistent with ngx_stat_reading/ngx_stat_writing, which are incremented on a per-request basis. The change adds ngx_stat_active increment for HTTP/3 request and push streams. --- src/http/v3/ngx_http_v3_request.c | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 793a34816..533a50fb8 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -78,6 +78,10 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream"); +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + hc = c->data; clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); -- cgit v1.2.3 From 0c33e484a4333fe2a343baf3aeefae3212534db3 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 6 Oct 2021 14:51:16 +0300 Subject: HTTP/3: fixed request length calculation. Previously, when request was blocked, r->request_length was not updated. --- src/http/v3/ngx_http_v3_request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 533a50fb8..44aef49e4 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -297,6 +297,8 @@ ngx_http_v3_process_request(ngx_event_t *rev) break; } + r->request_length += b->pos - p; + if (rc == NGX_BUSY) { if (rev->error) { ngx_http_close_request(r, NGX_HTTP_CLOSE); @@ -310,8 +312,6 @@ ngx_http_v3_process_request(ngx_event_t *rev) break; } - r->request_length += b->pos - p; - if (rc == NGX_AGAIN) { continue; } -- cgit v1.2.3 From 434f11bf3f4c9c8466a946c775441ecd6f768c13 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 7 Oct 2021 13:22:42 +0300 Subject: HTTP/3: traffic-based flood detection. With this patch, all traffic over HTTP/3 bidi and uni streams is counted in the h3c->total_bytes field, and payload traffic is counted in the h3c->payload_bytes field. As long as total traffic is many times larger than payload traffic, we consider this to be a flood. Request header traffic is counted as if all fields are literal. Response header traffic is counted as is. --- src/http/v3/ngx_http_v3_request.c | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 44aef49e4..bb9a72248 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -218,6 +218,7 @@ ngx_http_v3_process_request(ngx_event_t *rev) ngx_int_t rc; ngx_connection_t *c; ngx_http_request_t *r; + ngx_http_v3_session_t *h3c; ngx_http_core_srv_conf_t *cscf; ngx_http_v3_parse_headers_t *st; @@ -233,6 +234,8 @@ ngx_http_v3_process_request(ngx_event_t *rev) return; } + h3c = ngx_http_v3_get_session(c); + st = &r->v3_parse->headers; b = r->header_in; @@ -298,6 +301,12 @@ ngx_http_v3_process_request(ngx_event_t *rev) } r->request_length += b->pos - p; + h3c->total_bytes += b->pos - p; + + if (ngx_http_v3_check_flood(c) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_CLOSE); + break; + } if (rc == NGX_BUSY) { if (rev->error) { @@ -318,6 +327,10 @@ ngx_http_v3_process_request(ngx_event_t *rev) /* rc == NGX_OK || rc == NGX_DONE */ + h3c->payload_bytes += ngx_http_v3_encode_field_l(NULL, + &st->field_rep.field.name, + &st->field_rep.field.value); + if (ngx_http_v3_process_header(r, &st->field_rep.field.name, &st->field_rep.field.value) != NGX_OK) @@ -1080,6 +1093,7 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) ngx_buf_t *b; ngx_uint_t last; ngx_chain_t *cl, *out, *tl, **ll; + ngx_http_v3_session_t *h3c; ngx_http_request_body_t *rb; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; @@ -1088,6 +1102,8 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) rb = r->request_body; st = &r->v3_parse->body; + h3c = ngx_http_v3_get_session(r->connection); + if (rb->rest == -1) { ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -1135,6 +1151,11 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) rc = ngx_http_v3_parse_data(r->connection, st, cl->buf); r->request_length += cl->buf->pos - p; + h3c->total_bytes += cl->buf->pos - p; + + if (ngx_http_v3_check_flood(r->connection) != NGX_OK) { + return NGX_HTTP_CLOSE; + } if (rc == NGX_AGAIN) { continue; @@ -1178,6 +1199,8 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) { rb->received += st->length; r->request_length += st->length; + h3c->total_bytes += st->length; + h3c->payload_bytes += st->length; if (st->length < 8) { @@ -1222,12 +1245,16 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) cl->buf->pos += (size_t) st->length; rb->received += st->length; r->request_length += st->length; + h3c->total_bytes += st->length; + h3c->payload_bytes += st->length; st->length = 0; } else { st->length -= size; rb->received += size; r->request_length += size; + h3c->total_bytes += size; + h3c->payload_bytes += size; cl->buf->pos = cl->buf->last; } -- cgit v1.2.3 From 6118ec73cfef53897d00cc81810ee661321f0057 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 18 Oct 2021 15:22:33 +0300 Subject: HTTP/3: adjusted QUIC connection finalization. When an HTTP/3 function returns an error in context of a QUIC stream, it's this function's responsibility now to finalize the entire QUIC connection with the right code, if required. Previously, QUIC connection finalization could be done both outside and inside such functions. The new rule follows a similar rule for logging, leads to cleaner code, and allows to provide more details about the error. While here, a few error cases are no longer treated as fatal and QUIC connection is no longer finalized in these cases. A few other cases now lead to stream reset instead of connection finalization. --- src/http/v3/ngx_http_v3_request.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index bb9a72248..5c905bc3a 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -65,8 +65,6 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_http_core_srv_conf_t *cscf; if (ngx_http_v3_init_session(c) != NGX_OK) { - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "internal error"); ngx_http_close_connection(c); return; } @@ -110,8 +108,6 @@ ngx_http_v3_init(ngx_connection_t *c) h3c->goaway = 1; if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "goaway error"); ngx_http_close_connection(c); return; } @@ -287,15 +283,14 @@ ngx_http_v3_process_request(ngx_event_t *rev) rc = ngx_http_v3_parse_headers(c, st, b); if (rc > 0) { - ngx_http_v3_finalize_connection(c, rc, - "could not parse request headers"); + ngx_quic_reset_stream(c, rc); + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client sent invalid header"); ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); break; } if (rc == NGX_ERROR) { - ngx_http_v3_finalize_connection(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "internal error"); ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); break; } @@ -1167,17 +1162,13 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) } if (rc > 0) { - ngx_http_v3_finalize_connection(r->connection, rc, - "client sent invalid body"); + ngx_quic_reset_stream(r->connection, rc); ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "client sent invalid body"); return NGX_HTTP_BAD_REQUEST; } if (rc == NGX_ERROR) { - ngx_http_v3_finalize_connection(r->connection, - NGX_HTTP_V3_ERR_INTERNAL_ERROR, - "internal error"); return NGX_HTTP_INTERNAL_SERVER_ERROR; } -- cgit v1.2.3 From a6fb8fe85077bd10e11231c70ece803284890520 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 18 Oct 2021 15:47:06 +0300 Subject: HTTP/3: allowed QUIC stream connection reuse. A QUIC stream connection is treated as reusable until first bytes of request arrive, which is also when the request object is now allocated. A connection closed as a result of draining, is reset with the error code H3_REQUEST_REJECTED. Such behavior is allowed by quic-http-34: Once a request stream has been opened, the request MAY be cancelled by either endpoint. Clients cancel requests if the response is no longer of interest; servers cancel requests if they are unable to or choose not to respond. When the server cancels a request without performing any application processing, the request is considered "rejected." The server SHOULD abort its response stream with the error code H3_REQUEST_REJECTED. The client can treat requests rejected by the server as though they had never been sent at all, thereby allowing them to be retried later. --- src/http/v3/ngx_http_v3_request.c | 144 +++++++++++++++++++++++++++++++++----- 1 file changed, 127 insertions(+), 17 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 5c905bc3a..6f980ed0b 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,6 +10,7 @@ #include +static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); static void ngx_http_v3_cleanup_request(void *data); static void ngx_http_v3_process_request(ngx_event_t *rev); static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, @@ -53,12 +54,8 @@ static const struct { void ngx_http_v3_init(ngx_connection_t *c) { - size_t size; uint64_t n; - ngx_buf_t *b; ngx_event_t *rev; - ngx_pool_cleanup_t *cln; - ngx_http_request_t *r; ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; @@ -96,7 +93,7 @@ ngx_http_v3_init(ngx_connection_t *c) h3c = ngx_http_v3_get_session(c); if (h3c->goaway) { - ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); + c->close = 1; ngx_http_close_connection(c); return; } @@ -116,21 +113,57 @@ ngx_http_v3_init(ngx_connection_t *c) "reached maximum number of requests"); } - cln = ngx_pool_cleanup_add(c->pool, 0); - if (cln == NULL) { + rev = c->read; + rev->handler = ngx_http_v3_wait_request_handler; + c->write->handler = ngx_http_empty_handler; + + if (rev->ready) { + rev->handler(rev); + return; + } + + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_reusable_connection(c, 1); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_close_connection(c); return; } +} - cln->handler = ngx_http_v3_cleanup_request; - cln->data = c; - h3c->nrequests++; +static void +ngx_http_v3_wait_request_handler(ngx_event_t *rev) +{ + size_t size; + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_pool_cleanup_t *cln; + ngx_http_request_t *r; + ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; + ngx_http_core_srv_conf_t *cscf; - if (h3c->keepalive.timer_set) { - ngx_del_timer(&h3c->keepalive); + c = rev->data; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 wait request handler"); + + if (rev->timedout) { + ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out"); + c->timedout = 1; + ngx_http_close_connection(c); + return; } + if (c->close) { + ngx_http_close_connection(c); + return; + } + + hc = c->data; cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); size = cscf->client_header_buffer_size; @@ -159,8 +192,49 @@ ngx_http_v3_init(ngx_connection_t *c) b->end = b->last + size; } + n = c->recv(c, b->last, size); + + if (n == NGX_AGAIN) { + + if (!rev->timer_set) { + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_reusable_connection(c, 1); + } + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + /* + * We are trying to not hold c->buffer's memory for an idle connection. + */ + + if (ngx_pfree(c->pool, b->start) == NGX_OK) { + b->start = NULL; + } + + return; + } + + if (n == NGX_ERROR) { + ngx_http_close_connection(c); + return; + } + + if (n == 0) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client closed connection"); + ngx_http_close_connection(c); + return; + } + + b->last += n; + c->log->action = "reading client request"; + ngx_reusable_connection(c, 0); + r = ngx_http_create_request(c); if (r == NULL) { ngx_http_close_connection(c); @@ -171,7 +245,7 @@ ngx_http_v3_init(ngx_connection_t *c) r->v3_parse = ngx_pcalloc(r->pool, sizeof(ngx_http_v3_parse_t)); if (r->v3_parse == NULL) { - ngx_http_close_connection(c); + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); return; } @@ -179,23 +253,59 @@ ngx_http_v3_init(ngx_connection_t *c) * cscf->large_client_header_buffers.num; c->data = r; - c->requests = n + 1; + c->requests = (c->quic->id >> 2) + 1; - rev = c->read; - rev->handler = ngx_http_v3_process_request; + cln = ngx_pool_cleanup_add(r->pool, 0); + if (cln == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return; + } + + cln->handler = ngx_http_v3_cleanup_request; + cln->data = r; + h3c = ngx_http_v3_get_session(c); + h3c->nrequests++; + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } + + rev->handler = ngx_http_v3_process_request; ngx_http_v3_process_request(rev); } +void +ngx_http_v3_reset_connection(ngx_connection_t *c) +{ + if (c->timedout) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR); + + } else if (c->close) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_REQUEST_REJECTED); + + } else if (c->requests == 0 || c->error) { + ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_INTERNAL_ERROR); + } +} + + static void ngx_http_v3_cleanup_request(void *data) { - ngx_connection_t *c = data; + ngx_http_request_t *r = data; + ngx_connection_t *c; ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; + c = r->connection; + + if (!r->response_sent) { + c->error = 1; + } + h3c = ngx_http_v3_get_session(c); if (--h3c->nrequests == 0) { -- cgit v1.2.3 From 5c99f43e6f6f2e97a625bf00330f2f8d5af29815 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 18 Oct 2021 14:48:11 +0300 Subject: HTTP/3: send Stream Cancellation instruction. As per quic-qpack-21: When a stream is reset or reading is abandoned, the decoder emits a Stream Cancellation instruction. Previously the instruction was not sent. Now it's sent when closing QUIC stream connection if dynamic table capacity is non-zero and eof was not received from client. The latter condition means that a trailers section may still be on its way from client and the stream needs to be cancelled. --- src/http/v3/ngx_http_v3_request.c | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 6f980ed0b..e0c3a71ba 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -279,6 +279,14 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) void ngx_http_v3_reset_connection(ngx_connection_t *c) { + ngx_http_v3_srv_conf_t *h3scf; + + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (h3scf->max_table_capacity > 0 && !c->read->eof) { + (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); + } + if (c->timedout) { ngx_quic_reset_stream(c, NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR); -- cgit v1.2.3 From 731915a0c5e90b79d3cca1a4b0a3c33e1f77631c Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 6 Dec 2021 13:02:36 +0300 Subject: HTTP/3: merged ngx_http_quic_module into ngx_http_v3_module. --- src/http/v3/ngx_http_v3_request.c | 123 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 119 insertions(+), 4 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e0c3a71ba..a4b570370 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,6 +10,8 @@ #include +static void ngx_http_v3_init_hq_stream(ngx_connection_t *c); +static void ngx_http_v3_init_request_stream(ngx_connection_t *c); static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); static void ngx_http_v3_cleanup_request(void *data); static void ngx_http_v3_process_request(ngx_event_t *rev); @@ -53,24 +55,130 @@ static const struct { void ngx_http_v3_init(ngx_connection_t *c) +{ + ngx_http_connection_t *hc, *phc; + ngx_http_v3_srv_conf_t *h3scf; + ngx_http_core_loc_conf_t *clcf; + + hc = c->data; + + hc->ssl = 1; + + if (c->quic == NULL) { + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + + ngx_quic_run(c, &h3scf->quic); + + return; + } + + phc = ngx_http_quic_get_connection(c); + + if (phc->ssl_servername) { + hc->ssl_servername = phc->ssl_servername; + hc->conf_ctx = phc->conf_ctx; + + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + ngx_set_connection_log(c, clcf->error_log); + } + + if (!hc->addr_conf->http3) { + ngx_http_v3_init_hq_stream(c); + return; + } + + if (ngx_http_v3_init_session(c) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + ngx_http_v3_init_uni_stream(c); + + } else { + ngx_http_v3_init_request_stream(c); + } +} + + +static void +ngx_http_v3_init_hq_stream(ngx_connection_t *c) { uint64_t n; ngx_event_t *rev; ngx_http_connection_t *hc; - ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; ngx_http_core_srv_conf_t *cscf; - if (ngx_http_v3_init_session(c) != NGX_OK) { + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init hq stream"); + +#if (NGX_STAT_STUB) + (void) ngx_atomic_fetch_add(ngx_stat_active, 1); +#endif + + hc = c->data; + + /* Use HTTP/3 General Protocol Error Code 0x101 for finalization */ + + if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { + ngx_quic_finalize_connection(c->quic->parent, + NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, + "unexpected uni stream"); ngx_http_close_connection(c); return; } - if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - ngx_http_v3_init_uni_stream(c); + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + + n = c->quic->id >> 2; + + if (n >= clcf->keepalive_requests) { + ngx_quic_finalize_connection(c->quic->parent, + NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, + "reached maximum number of requests"); + ngx_http_close_connection(c); + return; + } + + if (ngx_current_msec - c->quic->parent->start_time + > clcf->keepalive_time) + { + ngx_quic_finalize_connection(c->quic->parent, + NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, + "reached maximum time for requests"); + ngx_http_close_connection(c); return; } + rev = c->read; + + if (rev->ready) { + rev->handler(rev); + return; + } + + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); + + ngx_add_timer(rev, cscf->client_header_timeout); + ngx_reusable_connection(c, 1); + + if (ngx_handle_read_event(rev, 0) != NGX_OK) { + ngx_http_close_connection(c); + return; + } +} + + +static void +ngx_http_v3_init_request_stream(ngx_connection_t *c) +{ + uint64_t n; + ngx_event_t *rev; + ngx_http_connection_t *hc; + ngx_http_v3_session_t *h3c; + ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init request stream"); #if (NGX_STAT_STUB) @@ -279,8 +387,15 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) void ngx_http_v3_reset_connection(ngx_connection_t *c) { + ngx_http_connection_t *hc; ngx_http_v3_srv_conf_t *h3scf; + hc = ngx_http_quic_get_connection(c); + + if (!hc->addr_conf->http3) { + return; + } + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); if (h3scf->max_table_capacity > 0 && !c->read->eof) { -- cgit v1.2.3 From d84c1f7885cc898f626057c314cdae4047c5d513 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Sat, 4 Dec 2021 10:52:55 +0300 Subject: HTTP/3: http3_hq directive and NGX_HTTP_V3_HQ macro. Listen quic parameter is no longer supported. --- src/http/v3/ngx_http_v3_request.c | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index a4b570370..7fce688aa 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,7 +10,9 @@ #include +#if (NGX_HTTP_V3_HQ) static void ngx_http_v3_init_hq_stream(ngx_connection_t *c); +#endif static void ngx_http_v3_init_request_stream(ngx_connection_t *c); static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); static void ngx_http_v3_cleanup_request(void *data); @@ -64,11 +66,10 @@ ngx_http_v3_init(ngx_connection_t *c) hc->ssl = 1; - if (c->quic == NULL) { - h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); + if (c->quic == NULL) { ngx_quic_run(c, &h3scf->quic); - return; } @@ -82,10 +83,12 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_set_connection_log(c, clcf->error_log); } - if (!hc->addr_conf->http3) { +#if (NGX_HTTP_V3_HQ) + if (h3scf->hq) { ngx_http_v3_init_hq_stream(c); return; } +#endif if (ngx_http_v3_init_session(c) != NGX_OK) { ngx_http_close_connection(c); @@ -101,6 +104,8 @@ ngx_http_v3_init(ngx_connection_t *c) } +#if (NGX_HTTP_V3_HQ) + static void ngx_http_v3_init_hq_stream(ngx_connection_t *c) { @@ -168,6 +173,8 @@ ngx_http_v3_init_hq_stream(ngx_connection_t *c) } } +#endif + static void ngx_http_v3_init_request_stream(ngx_connection_t *c) @@ -387,16 +394,15 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) void ngx_http_v3_reset_connection(ngx_connection_t *c) { - ngx_http_connection_t *hc; ngx_http_v3_srv_conf_t *h3scf; - hc = ngx_http_quic_get_connection(c); + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); - if (!hc->addr_conf->http3) { +#if (NGX_HTTP_V3_HQ) + if (h3scf->hq) { return; } - - h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); +#endif if (h3scf->max_table_capacity > 0 && !c->read->eof) { (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); -- cgit v1.2.3 From 0791b508807eac65681c9c33d27acece67a9a421 Mon Sep 17 00:00:00 2001 From: Vladimir Homutov Date: Mon, 6 Dec 2021 15:19:54 +0300 Subject: QUIC: simplified configuration. Directives that set transport parameters are removed from the configuration. Corresponding values are derived from the quic configuration or initialized to default. Whenever possible, quic configuration parameters are taken from higher-level protocol settings, i.e. HTTP/3. --- src/http/v3/ngx_http_v3_request.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 7fce688aa..e103a7eca 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -66,9 +66,11 @@ ngx_http_v3_init(ngx_connection_t *c) hc->ssl = 1; + clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); if (c->quic == NULL) { + h3scf->quic.timeout = clcf->keepalive_timeout; ngx_quic_run(c, &h3scf->quic); return; } @@ -79,7 +81,6 @@ ngx_http_v3_init(ngx_connection_t *c) hc->ssl_servername = phc->ssl_servername; hc->conf_ctx = phc->conf_ctx; - clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); ngx_set_connection_log(c, clcf->error_log); } -- cgit v1.2.3 From 9860a82b195afb9c8d7a98536c752927b677736b Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 7 Dec 2021 15:49:30 +0300 Subject: HTTP/3: avoid sending stream cancellation for pushed streams. --- src/http/v3/ngx_http_v3_request.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e103a7eca..6faa3ee0b 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -405,7 +405,9 @@ ngx_http_v3_reset_connection(ngx_connection_t *c) } #endif - if (h3scf->max_table_capacity > 0 && !c->read->eof) { + if (h3scf->max_table_capacity > 0 && !c->read->eof + && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) + { (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); } -- cgit v1.2.3 From 7f0fdd4e149380c9439e63bea7625c5071781af6 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 29 Dec 2021 15:33:51 +0300 Subject: Style. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 6faa3ee0b..b97caec95 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -628,7 +628,7 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, } } - if (name->len && name->data[0] == ':') { + if (name->len && name->data[0] == ':') { return ngx_http_v3_process_pseudo_header(r, name, value); } -- cgit v1.2.3 From b1356ade078a8e4092943a2b7b5d3f92dd4def93 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Thu, 30 Dec 2021 12:59:32 +0300 Subject: HTTP/3: improved processing of multiple Cookie field lines. As per draft-ietf-quic-http, 4.1.1.2, and similar to HTTP/2 specification, they ought to be concatenated. This closely follows ngx_http_v2_module. --- src/http/v3/ngx_http_v3_request.c | 165 ++++++++++++++++++++++++++++++++++---- 1 file changed, 151 insertions(+), 14 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index b97caec95..4dbda3596 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -25,6 +25,8 @@ static ngx_int_t ngx_http_v3_process_pseudo_header(ngx_http_request_t *r, ngx_str_t *name, ngx_str_t *value); static ngx_int_t ngx_http_v3_init_pseudo_headers(ngx_http_request_t *r); static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r); +static ngx_int_t ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value); +static ngx_int_t ngx_http_v3_construct_cookie_header(ngx_http_request_t *r); static void ngx_http_v3_read_client_request_body_handler(ngx_http_request_t *r); static ngx_int_t ngx_http_v3_do_read_client_request_body(ngx_http_request_t *r); static ngx_int_t ngx_http_v3_request_body_filter(ngx_http_request_t *r, @@ -601,6 +603,8 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, ngx_http_core_srv_conf_t *cscf; ngx_http_core_main_conf_t *cmcf; + static ngx_str_t cookie = ngx_string("cookie"); + len = name->len + value->len; if (len > r->v3_parse->header_limit) { @@ -636,24 +640,34 @@ ngx_http_v3_process_header(ngx_http_request_t *r, ngx_str_t *name, return NGX_ERROR; } - h = ngx_list_push(&r->headers_in.headers); - if (h == NULL) { - ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); - return NGX_ERROR; - } + if (name->len == cookie.len + && ngx_memcmp(name->data, cookie.data, cookie.len) == 0) + { + if (ngx_http_v3_cookie(r, value) != NGX_OK) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } - h->key = *name; - h->value = *value; - h->lowcase_key = h->key.data; - h->hash = ngx_hash_key(h->key.data, h->key.len); + } else { + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } - cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + h->key = *name; + h->value = *value; + h->lowcase_key = h->key.data; + h->hash = ngx_hash_key(h->key.data, h->key.len); - hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, - h->lowcase_key, h->key.len); + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); - if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { - return NGX_ERROR; + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh && hh->handler(r, h, hh->offset) != NGX_OK) { + return NGX_ERROR; + } } ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, @@ -981,6 +995,10 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) return NGX_ERROR; } + if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { + return NGX_ERROR; + } + if (r->headers_in.server.len == 0) { ngx_log_error(NGX_LOG_INFO, c->log, 0, "client sent neither \":authority\" nor \"Host\" header"); @@ -1056,6 +1074,125 @@ failed: } +static ngx_int_t +ngx_http_v3_cookie(ngx_http_request_t *r, ngx_str_t *value) +{ + ngx_str_t *val; + ngx_array_t *cookies; + + cookies = r->v3_parse->cookies; + + if (cookies == NULL) { + cookies = ngx_array_create(r->pool, 2, sizeof(ngx_str_t)); + if (cookies == NULL) { + return NGX_ERROR; + } + + r->v3_parse->cookies = cookies; + } + + val = ngx_array_push(cookies); + if (val == NULL) { + return NGX_ERROR; + } + + *val = *value; + + return NGX_OK; +} + + +static ngx_int_t +ngx_http_v3_construct_cookie_header(ngx_http_request_t *r) +{ + u_char *buf, *p, *end; + size_t len; + ngx_str_t *vals; + ngx_uint_t i; + ngx_array_t *cookies; + ngx_table_elt_t *h; + ngx_http_header_t *hh; + ngx_http_core_main_conf_t *cmcf; + + static ngx_str_t cookie = ngx_string("cookie"); + + cookies = r->v3_parse->cookies; + + if (cookies == NULL) { + return NGX_OK; + } + + vals = cookies->elts; + + i = 0; + len = 0; + + do { + len += vals[i].len + 2; + } while (++i != cookies->nelts); + + len -= 2; + + buf = ngx_pnalloc(r->pool, len + 1); + if (buf == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + p = buf; + end = buf + len; + + for (i = 0; /* void */ ; i++) { + + p = ngx_cpymem(p, vals[i].data, vals[i].len); + + if (p == end) { + *p = '\0'; + break; + } + + *p++ = ';'; *p++ = ' '; + } + + h = ngx_list_push(&r->headers_in.headers); + if (h == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + h->hash = ngx_hash(ngx_hash(ngx_hash(ngx_hash( + ngx_hash('c', 'o'), 'o'), 'k'), 'i'), 'e'); + + h->key.len = cookie.len; + h->key.data = cookie.data; + + h->value.len = len; + h->value.data = buf; + + h->lowcase_key = cookie.data; + + cmcf = ngx_http_get_module_main_conf(r, ngx_http_core_module); + + hh = ngx_hash_find(&cmcf->headers_in_hash, h->hash, + h->lowcase_key, h->key.len); + + if (hh == NULL) { + ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); + return NGX_ERROR; + } + + if (hh->handler(r, h, hh->offset) != NGX_OK) { + /* + * request has been finalized already + * in ngx_http_process_multi_header_lines() + */ + return NGX_ERROR; + } + + return NGX_OK; +} + + ngx_int_t ngx_http_v3_read_request_body(ngx_http_request_t *r) { -- cgit v1.2.3 From 5cde1259b2de257ce380813f03ef874c37632423 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 3 Aug 2022 16:59:51 +0400 Subject: HTTP/3: skip empty request body buffers (ticket #2374). When client DATA frame header and its content come in different QUIC packets, it may happen that only the header is processed by the first ngx_http_v3_request_body_filter() call. In this case an empty request body buffer is added to r->request_body->bufs, which is later reused in a subsequent ngx_http_v3_request_body_filter() call without being removed from the body chain. As a result, rb->request_body->bufs ends up with two copies of the same buffer. The fix is to avoid adding empty request body buffers to r->request_body->bufs. --- src/http/v3/ngx_http_v3_request.c | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 4dbda3596..14802249b 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1552,15 +1552,17 @@ ngx_http_v3_request_body_filter(ngx_http_request_t *r, ngx_chain_t *in) } /* rc == NGX_OK */ - } - if (max != -1 && (uint64_t) (max - rb->received) < st->length) { - ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, - "client intended to send too large " - "body: %O+%ui bytes", - rb->received, st->length); + if (max != -1 && (uint64_t) (max - rb->received) < st->length) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "client intended to send too large " + "body: %O+%ui bytes", + rb->received, st->length); - return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + return NGX_HTTP_REQUEST_ENTITY_TOO_LARGE; + } + + continue; } if (b -- cgit v1.2.3 From 36d80a52693904d829d9a4e27361bcd29e41d48d Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 22 Nov 2022 14:10:04 +0400 Subject: HTTP/3: fixed server_name regex captures (ticket #2407). Previously, HTTP/3 stream connection didn't inherit the servername regex from the main QUIC connection saved when processing SNI and using regular expressions in server names. As a result, it didn't execute to set regex captures when choosing the virtual server while parsing HTTP/3 headers. --- src/http/v3/ngx_http_v3_request.c | 1 + 1 file changed, 1 insertion(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 14802249b..7921d8dc5 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -81,6 +81,7 @@ ngx_http_v3_init(ngx_connection_t *c) if (phc->ssl_servername) { hc->ssl_servername = phc->ssl_servername; + hc->ssl_servername_regex = phc->ssl_servername_regex; hc->conf_ctx = phc->conf_ctx; ngx_set_connection_log(c, clcf->error_log); -- cgit v1.2.3 From 7d73c50a2d11314270663ebfa4665719c66634f4 Mon Sep 17 00:00:00 2001 From: Jiuzhou Cui Date: Fri, 25 Nov 2022 15:07:23 +0800 Subject: HTTP/3: fixed build without NGX_PCRE (broken by 0f5fc7a320db). --- src/http/v3/ngx_http_v3_request.c | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 7921d8dc5..969fdf356 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -81,7 +81,9 @@ ngx_http_v3_init(ngx_connection_t *c) if (phc->ssl_servername) { hc->ssl_servername = phc->ssl_servername; +#if (NGX_PCRE) hc->ssl_servername_regex = phc->ssl_servername_regex; +#endif hc->conf_ctx = phc->conf_ctx; ngx_set_connection_log(c, clcf->error_log); -- cgit v1.2.3 From dd4c31fc34bd8390dd3fa9e804afc15b57b69135 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 19 Oct 2022 17:45:30 +0400 Subject: HTTP/3: unified hq code with regular HTTP/3 code. The change removes hq-specific request handler. Now hq requests are handled by the HTTP/3 request handler. --- src/http/v3/ngx_http_v3_request.c | 118 ++++++++------------------------------ 1 file changed, 23 insertions(+), 95 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 969fdf356..bdb5331fe 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -10,9 +10,6 @@ #include -#if (NGX_HTTP_V3_HQ) -static void ngx_http_v3_init_hq_stream(ngx_connection_t *c); -#endif static void ngx_http_v3_init_request_stream(ngx_connection_t *c); static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); static void ngx_http_v3_cleanup_request(void *data); @@ -89,13 +86,6 @@ ngx_http_v3_init(ngx_connection_t *c) ngx_set_connection_log(c, clcf->error_log); } -#if (NGX_HTTP_V3_HQ) - if (h3scf->hq) { - ngx_http_v3_init_hq_stream(c); - return; - } -#endif - if (ngx_http_v3_init_session(c) != NGX_OK) { ngx_http_close_connection(c); return; @@ -110,83 +100,12 @@ ngx_http_v3_init(ngx_connection_t *c) } -#if (NGX_HTTP_V3_HQ) - -static void -ngx_http_v3_init_hq_stream(ngx_connection_t *c) -{ - uint64_t n; - ngx_event_t *rev; - ngx_http_connection_t *hc; - ngx_http_core_loc_conf_t *clcf; - ngx_http_core_srv_conf_t *cscf; - - ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init hq stream"); - -#if (NGX_STAT_STUB) - (void) ngx_atomic_fetch_add(ngx_stat_active, 1); -#endif - - hc = c->data; - - /* Use HTTP/3 General Protocol Error Code 0x101 for finalization */ - - if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { - ngx_quic_finalize_connection(c->quic->parent, - NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, - "unexpected uni stream"); - ngx_http_close_connection(c); - return; - } - - clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); - - n = c->quic->id >> 2; - - if (n >= clcf->keepalive_requests) { - ngx_quic_finalize_connection(c->quic->parent, - NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, - "reached maximum number of requests"); - ngx_http_close_connection(c); - return; - } - - if (ngx_current_msec - c->quic->parent->start_time - > clcf->keepalive_time) - { - ngx_quic_finalize_connection(c->quic->parent, - NGX_HTTP_V3_ERR_GENERAL_PROTOCOL_ERROR, - "reached maximum time for requests"); - ngx_http_close_connection(c); - return; - } - - rev = c->read; - - if (rev->ready) { - rev->handler(rev); - return; - } - - cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); - - ngx_add_timer(rev, cscf->client_header_timeout); - ngx_reusable_connection(c, 1); - - if (ngx_handle_read_event(rev, 0) != NGX_OK) { - ngx_http_close_connection(c); - return; - } -} - -#endif - - static void ngx_http_v3_init_request_stream(ngx_connection_t *c) { uint64_t n; ngx_event_t *rev; + ngx_connection_t *pc; ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; @@ -219,15 +138,21 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) return; } + pc = c->quic->parent; + if (n + 1 == clcf->keepalive_requests - || ngx_current_msec - c->quic->parent->start_time - > clcf->keepalive_time) + || ngx_current_msec - pc->start_time > clcf->keepalive_time) { h3c->goaway = 1; - if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { - ngx_http_close_connection(c); - return; +#if (NGX_HTTP_V3_HQ) + if (!h3c->hq) +#endif + { + if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { + ngx_http_close_connection(c); + return; + } } ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, @@ -235,8 +160,14 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) } rev = c->read; - rev->handler = ngx_http_v3_wait_request_handler; - c->write->handler = ngx_http_empty_handler; + +#if (NGX_HTTP_V3_HQ) + if (!h3c->hq) +#endif + { + rev->handler = ngx_http_v3_wait_request_handler; + c->write->handler = ngx_http_empty_handler; + } if (rev->ready) { rev->handler(rev); @@ -264,8 +195,8 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) ngx_connection_t *c; ngx_pool_cleanup_t *cln; ngx_http_request_t *r; - ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; + ngx_http_connection_t *hc; ngx_http_core_srv_conf_t *cscf; c = rev->data; @@ -404,13 +335,10 @@ ngx_http_v3_reset_connection(ngx_connection_t *c) h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + if (h3scf->max_table_capacity > 0 && !c->read->eof #if (NGX_HTTP_V3_HQ) - if (h3scf->hq) { - return; - } + && !h3scf->hq #endif - - if (h3scf->max_table_capacity > 0 && !c->read->eof && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); -- cgit v1.2.3 From fed44881d3bf5126b49144dba58f2830e0fe9866 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 19 Oct 2022 17:45:18 +0400 Subject: QUIC: idle mode for main connection. Now main QUIC connection for HTTP/3 always has c->idle flag set. This allows the connection to receive worker shutdown notification. It is passed to application level via a new conf->shutdown() callback. The HTTP/3 shutdown callback sends GOAWAY to client and gracefully shuts down the QUIC connection. --- src/http/v3/ngx_http_v3_request.c | 35 ++++++++++++++++++++++++++++++++++- 1 file changed, 34 insertions(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index bdb5331fe..c75bc19c5 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -100,6 +100,37 @@ ngx_http_v3_init(ngx_connection_t *c) } +void +ngx_http_v3_shutdown(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 shutdown"); + + h3c = ngx_http_v3_get_session(c); + + if (h3c == NULL) { + ngx_quic_finalize_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "connection shutdown"); + return; + } + + if (!h3c->goaway) { + h3c->goaway = 1; + +#if (NGX_HTTP_V3_HQ) + if (!h3c->hq) +#endif + { + (void) ngx_http_v3_send_goaway(c, h3c->next_request_id); + } + + ngx_http_v3_shutdown_connection(c, NGX_HTTP_V3_ERR_NO_ERROR, + "connection shutdown"); + } +} + + static void ngx_http_v3_init_request_stream(ngx_connection_t *c) { @@ -140,6 +171,8 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) pc = c->quic->parent; + h3c->next_request_id = c->quic->id + 0x04; + if (n + 1 == clcf->keepalive_requests || ngx_current_msec - pc->start_time > clcf->keepalive_time) { @@ -149,7 +182,7 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) if (!h3c->hq) #endif { - if (ngx_http_v3_send_goaway(c, (n + 1) << 2) != NGX_OK) { + if (ngx_http_v3_send_goaway(c, h3c->next_request_id) != NGX_OK) { ngx_http_close_connection(c); return; } -- cgit v1.2.3 From 8a1deaca78d65f4db82f856e22dcf879d1cec479 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 22 Aug 2022 14:09:03 +0400 Subject: HTTP/3: renamed functions. ngx_http_v3_init() is renamed ngx_http_v3_init_stream(). ngx_http_v3_reset_connection() is renamed to ngx_http_v3_reset_stream(). --- src/http/v3/ngx_http_v3_request.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index c75bc19c5..cbfede836 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -55,7 +55,7 @@ static const struct { void -ngx_http_v3_init(ngx_connection_t *c) +ngx_http_v3_init_stream(ngx_connection_t *c) { ngx_http_connection_t *hc, *phc; ngx_http_v3_srv_conf_t *h3scf; @@ -362,7 +362,7 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) void -ngx_http_v3_reset_connection(ngx_connection_t *c) +ngx_http_v3_reset_stream(ngx_connection_t *c) { ngx_http_v3_srv_conf_t *h3scf; -- cgit v1.2.3 From 64ccdf45288c46b5f8e12426d3802c44d789d115 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Wed, 30 Nov 2022 12:51:15 +0400 Subject: QUIC: application init() callback. It's called after handshake completion or prior to the first early data stream creation. The callback should initialize application-level data before creating streams. HTTP/3 callback implementation sets keepalive timer and sends SETTINGS. Also, this allows to limit max handshake time in ngx_http_v3_init_stream(). --- src/http/v3/ngx_http_v3_request.c | 38 +++++++++++++++++++++++++++++++++----- 1 file changed, 33 insertions(+), 5 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index cbfede836..2e7dd811d 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -57,18 +57,29 @@ static const struct { void ngx_http_v3_init_stream(ngx_connection_t *c) { + ngx_http_v3_session_t *h3c; ngx_http_connection_t *hc, *phc; ngx_http_v3_srv_conf_t *h3scf; ngx_http_core_loc_conf_t *clcf; + ngx_http_core_srv_conf_t *cscf; hc = c->data; hc->ssl = 1; clcf = ngx_http_get_module_loc_conf(hc->conf_ctx, ngx_http_core_module); + cscf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_core_module); h3scf = ngx_http_get_module_srv_conf(hc->conf_ctx, ngx_http_v3_module); if (c->quic == NULL) { + if (ngx_http_v3_init_session(c) != NGX_OK) { + ngx_http_close_connection(c); + return; + } + + h3c = hc->v3_session; + ngx_add_timer(&h3c->keepalive, cscf->client_header_timeout); + h3scf->quic.timeout = clcf->keepalive_timeout; ngx_quic_run(c, &h3scf->quic); return; @@ -86,11 +97,6 @@ ngx_http_v3_init_stream(ngx_connection_t *c) ngx_set_connection_log(c, clcf->error_log); } - if (ngx_http_v3_init_session(c) != NGX_OK) { - ngx_http_close_connection(c); - return; - } - if (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) { ngx_http_v3_init_uni_stream(c); @@ -100,6 +106,28 @@ ngx_http_v3_init_stream(ngx_connection_t *c) } +ngx_int_t +ngx_http_v3_init(ngx_connection_t *c) +{ + ngx_http_v3_session_t *h3c; + ngx_http_core_loc_conf_t *clcf; + + ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); + + h3c = ngx_http_v3_get_session(c); + clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); + ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); + +#if (NGX_HTTP_V3_HQ) + if (h3c->hq) { + return NGX_OK; + } +#endif + + return ngx_http_v3_send_settings(c); +} + + void ngx_http_v3_shutdown(ngx_connection_t *c) { -- cgit v1.2.3 From 36f7b31f9578c0d393cfe82d4e23c76a7539f34e Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Tue, 25 Oct 2022 12:52:09 +0400 Subject: HTTP/3: implement keepalive for hq. Previously, keepalive timer was deleted in ngx_http_v3_wait_request_handler() and set in request cleanup handler. This worked for HTTP/3 connections, but not for hq connections. Now keepalive timer is deleted in ngx_http_v3_init_request_stream() and set in connection cleanup handler, which works both for HTTP/3 and hq. --- src/http/v3/ngx_http_v3_request.c | 47 +++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 17 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 2e7dd811d..f05198903 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -12,6 +12,7 @@ static void ngx_http_v3_init_request_stream(ngx_connection_t *c); static void ngx_http_v3_wait_request_handler(ngx_event_t *rev); +static void ngx_http_v3_cleanup_connection(void *data); static void ngx_http_v3_cleanup_request(void *data); static void ngx_http_v3_process_request(ngx_event_t *rev); static ngx_int_t ngx_http_v3_process_header(ngx_http_request_t *r, @@ -165,6 +166,7 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) uint64_t n; ngx_event_t *rev; ngx_connection_t *pc; + ngx_pool_cleanup_t *cln; ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; @@ -220,6 +222,21 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) "reached maximum number of requests"); } + cln = ngx_pool_cleanup_add(c->pool, 0); + if (cln == NULL) { + ngx_http_close_connection(c); + return; + } + + cln->handler = ngx_http_v3_cleanup_connection; + cln->data = c; + + h3c->nrequests++; + + if (h3c->keepalive.timer_set) { + ngx_del_timer(&h3c->keepalive); + } + rev = c->read; #if (NGX_HTTP_V3_HQ) @@ -256,7 +273,6 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) ngx_connection_t *c; ngx_pool_cleanup_t *cln; ngx_http_request_t *r; - ngx_http_v3_session_t *h3c; ngx_http_connection_t *hc; ngx_http_core_srv_conf_t *cscf; @@ -377,13 +393,6 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) cln->handler = ngx_http_v3_cleanup_request; cln->data = r; - h3c = ngx_http_v3_get_session(c); - h3c->nrequests++; - - if (h3c->keepalive.timer_set) { - ngx_del_timer(&h3c->keepalive); - } - rev->handler = ngx_http_v3_process_request; ngx_http_v3_process_request(rev); } @@ -418,20 +427,13 @@ ngx_http_v3_reset_stream(ngx_connection_t *c) static void -ngx_http_v3_cleanup_request(void *data) +ngx_http_v3_cleanup_connection(void *data) { - ngx_http_request_t *r = data; + ngx_connection_t *c = data; - ngx_connection_t *c; ngx_http_v3_session_t *h3c; ngx_http_core_loc_conf_t *clcf; - c = r->connection; - - if (!r->response_sent) { - c->error = 1; - } - h3c = ngx_http_v3_get_session(c); if (--h3c->nrequests == 0) { @@ -441,6 +443,17 @@ ngx_http_v3_cleanup_request(void *data) } +static void +ngx_http_v3_cleanup_request(void *data) +{ + ngx_http_request_t *r = data; + + if (!r->response_sent) { + r->connection->error = 1; + } +} + + static void ngx_http_v3_process_request(ngx_event_t *rev) { -- cgit v1.2.3 From 1fe0913fccedfffade10a88d3fb3033339a42900 Mon Sep 17 00:00:00 2001 From: Sergey Kandaurov Date: Tue, 10 Jan 2023 17:59:16 +0400 Subject: HTTP/3: fixed $connection_time. Previously, start_time wasn't set for a new stream. The fix is to derive it from the parent connection. Also it's used to simplify tracking keepalive_time. --- src/http/v3/ngx_http_v3_request.c | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index f05198903..8a5aeeb14 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -165,7 +165,6 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) { uint64_t n; ngx_event_t *rev; - ngx_connection_t *pc; ngx_pool_cleanup_t *cln; ngx_http_connection_t *hc; ngx_http_v3_session_t *h3c; @@ -199,12 +198,10 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) return; } - pc = c->quic->parent; - h3c->next_request_id = c->quic->id + 0x04; if (n + 1 == clcf->keepalive_requests - || ngx_current_msec - pc->start_time > clcf->keepalive_time) + || ngx_current_msec - c->start_time > clcf->keepalive_time) { h3c->goaway = 1; -- cgit v1.2.3 From faa655f211612fcdac938d58095a4ab7f03969a9 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 5 Jan 2023 18:15:46 +0400 Subject: HTTP/3: trigger 400 (Bad Request) on stream error while blocked. Previously, stream was closed with NGX_HTTP_CLOSE. However, in a similar case when recv() returns eof or error, status 400 is triggered. --- src/http/v3/ngx_http_v3_request.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 8a5aeeb14..e8b84eabd 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -551,7 +551,7 @@ ngx_http_v3_process_request(ngx_event_t *rev) if (rc == NGX_BUSY) { if (rev->error) { - ngx_http_close_request(r, NGX_HTTP_CLOSE); + ngx_http_finalize_request(r, NGX_HTTP_BAD_REQUEST); break; } -- cgit v1.2.3 From 0065ba68b08b8cf4eaf3c18266d1a93182f196ed Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 5 Jan 2023 19:03:22 +0400 Subject: HTTP/3: insert count block timeout. Previously, there was no timeout for a request stream blocked on insert count, which could result in infinite wait. Now client_header_timeout is set when stream is first blocked. --- src/http/v3/ngx_http_v3_request.c | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index e8b84eabd..7bf61459f 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -555,6 +555,12 @@ ngx_http_v3_process_request(ngx_event_t *rev) break; } + if (!rev->timer_set) { + cscf = ngx_http_get_module_srv_conf(r, + ngx_http_core_module); + ngx_add_timer(rev, cscf->client_header_timeout); + } + if (ngx_handle_read_event(rev, 0) != NGX_OK) { ngx_http_close_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR); } -- cgit v1.2.3 From 815ef96124176baef4e940c4beaec158305b368a Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Mon, 27 Feb 2023 14:00:56 +0400 Subject: HTTP/3: "quic" parameter of "listen" directive. Now "listen" directve has a new "quic" parameter which enables QUIC protocol for the address. Further, to enable HTTP/3, a new directive "http3" is introduced. The hq-interop protocol is enabled by "http3_hq" as before. Now application protocol is chosen by ALPN. Previously used "http3" parameter of "listen" is deprecated. --- src/http/v3/ngx_http_v3_request.c | 67 +++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 23 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index 7bf61459f..ff6b40734 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -110,7 +110,10 @@ ngx_http_v3_init_stream(ngx_connection_t *c) ngx_int_t ngx_http_v3_init(ngx_connection_t *c) { + unsigned int len; + const unsigned char *data; ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; ngx_http_core_loc_conf_t *clcf; ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0, "http3 init"); @@ -119,11 +122,23 @@ ngx_http_v3_init(ngx_connection_t *c) clcf = ngx_http_v3_get_module_loc_conf(c, ngx_http_core_module); ngx_add_timer(&h3c->keepalive, clcf->keepalive_timeout); -#if (NGX_HTTP_V3_HQ) - if (h3c->hq) { - return NGX_OK; + h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); + + if (h3scf->enable_hq) { + if (!h3scf->enable) { + h3c->hq = 1; + return NGX_OK; + } + + SSL_get0_alpn_selected(c->ssl->connection, &data, &len); + + if (len == sizeof(NGX_HTTP_V3_HQ_PROTO) - 1 + && ngx_strncmp(data, NGX_HTTP_V3_HQ_PROTO, len) == 0) + { + h3c->hq = 1; + return NGX_OK; + } } -#endif return ngx_http_v3_send_settings(c); } @@ -147,10 +162,7 @@ ngx_http_v3_shutdown(ngx_connection_t *c) if (!h3c->goaway) { h3c->goaway = 1; -#if (NGX_HTTP_V3_HQ) - if (!h3c->hq) -#endif - { + if (!h3c->hq) { (void) ngx_http_v3_send_goaway(c, h3c->next_request_id); } @@ -205,10 +217,7 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) { h3c->goaway = 1; -#if (NGX_HTTP_V3_HQ) - if (!h3c->hq) -#endif - { + if (!h3c->hq) { if (ngx_http_v3_send_goaway(c, h3c->next_request_id) != NGX_OK) { ngx_http_close_connection(c); return; @@ -236,10 +245,7 @@ ngx_http_v3_init_request_stream(ngx_connection_t *c) rev = c->read; -#if (NGX_HTTP_V3_HQ) - if (!h3c->hq) -#endif - { + if (!h3c->hq) { rev->handler = ngx_http_v3_wait_request_handler; c->write->handler = ngx_http_empty_handler; } @@ -398,14 +404,14 @@ ngx_http_v3_wait_request_handler(ngx_event_t *rev) void ngx_http_v3_reset_stream(ngx_connection_t *c) { + ngx_http_v3_session_t *h3c; ngx_http_v3_srv_conf_t *h3scf; h3scf = ngx_http_v3_get_module_srv_conf(c, ngx_http_v3_module); - if (h3scf->max_table_capacity > 0 && !c->read->eof -#if (NGX_HTTP_V3_HQ) - && !h3scf->hq -#endif + h3c = ngx_http_v3_get_session(c); + + if (h3scf->max_table_capacity > 0 && !c->read->eof && !h3c->hq && (c->quic->id & NGX_QUIC_STREAM_UNIDIRECTIONAL) == 0) { (void) ngx_http_v3_send_cancel_stream(c, c->quic->id); @@ -993,9 +999,11 @@ failed: static ngx_int_t ngx_http_v3_process_request_header(ngx_http_request_t *r) { - ssize_t n; - ngx_buf_t *b; - ngx_connection_t *c; + ssize_t n; + ngx_buf_t *b; + ngx_connection_t *c; + ngx_http_v3_session_t *h3c; + ngx_http_v3_srv_conf_t *h3scf; c = r->connection; @@ -1003,6 +1011,19 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) return NGX_ERROR; } + h3c = ngx_http_v3_get_session(c); + h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); + + if (!r->http_connection->addr_conf->http3) { + if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client attempted to request the server name " + "for which the negotiated protocol is disabled"); + ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); + return NGX_ERROR; + } + } + if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { return NGX_ERROR; } -- cgit v1.2.3 From 2ce3eeeeb76318e414b62d399da70872d2de23d8 Mon Sep 17 00:00:00 2001 From: Roman Arutyunyan Date: Thu, 11 May 2023 13:22:10 +0400 Subject: HTTP/3: removed "http3" parameter of "listen" directive. The parameter has been deprecated since c851a2ed5ce8. --- src/http/v3/ngx_http_v3_request.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) (limited to 'src/http/v3/ngx_http_v3_request.c') diff --git a/src/http/v3/ngx_http_v3_request.c b/src/http/v3/ngx_http_v3_request.c index ff6b40734..6f72dc402 100644 --- a/src/http/v3/ngx_http_v3_request.c +++ b/src/http/v3/ngx_http_v3_request.c @@ -1014,14 +1014,12 @@ ngx_http_v3_process_request_header(ngx_http_request_t *r) h3c = ngx_http_v3_get_session(c); h3scf = ngx_http_get_module_srv_conf(r, ngx_http_v3_module); - if (!r->http_connection->addr_conf->http3) { - if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { - ngx_log_error(NGX_LOG_INFO, c->log, 0, - "client attempted to request the server name " - "for which the negotiated protocol is disabled"); - ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); - return NGX_ERROR; - } + if ((h3c->hq && !h3scf->enable_hq) || (!h3c->hq && !h3scf->enable)) { + ngx_log_error(NGX_LOG_INFO, c->log, 0, + "client attempted to request the server name " + "for which the negotiated protocol is disabled"); + ngx_http_finalize_request(r, NGX_HTTP_MISDIRECTED_REQUEST); + return NGX_ERROR; } if (ngx_http_v3_construct_cookie_header(r) != NGX_OK) { -- cgit v1.2.3