diff --git a/src/http/modules/ngx_http_proxy_v2_module.c b/src/http/modules/ngx_http_proxy_v2_module.c index 010c02a86..cb653439a 100644 --- a/src/http/modules/ngx_http_proxy_v2_module.c +++ b/src/http/modules/ngx_http_proxy_v2_module.c @@ -77,6 +77,7 @@ typedef struct { ngx_str_t value; u_char *field_end; + size_t header_limit; size_t field_length; size_t field_rest; u_char field_state; @@ -978,6 +979,14 @@ ngx_http_proxy_v2_reinit_request(ngx_http_request_t *r) } ctx->state = 0; + ctx->frame_state = 0; + ctx->fragment_state = 0; + ctx->rest = 0; + ctx->stream_id = 0; + ctx->type = 0; + ctx->flags = 0; + ctx->padding = 0; + ctx->error = 0; ctx->header_sent = 0; ctx->output_closed = 0; ctx->output_blocked = 0; @@ -1480,6 +1489,10 @@ ngx_http_proxy_v2_process_header(ngx_http_request_t *r) return NGX_HTTP_UPSTREAM_INVALID_HEADER; } + if (rc == NGX_HTTP_UPSTREAM_RETRY) { + return NGX_HTTP_UPSTREAM_RETRY; + } + if (rc == NGX_OK) { continue; } @@ -1961,7 +1974,9 @@ ngx_http_proxy_v2_process_control_frame(ngx_http_request_t *r, if (ctx->stream_id < ctx->id) { - /* TODO: we can retry non-idempotent requests */ + if (ctx->error == 0) { + return NGX_HTTP_UPSTREAM_RETRY; + } ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent goaway with error %ui", @@ -2250,6 +2265,10 @@ ngx_http_proxy_v2_process_frames(ngx_http_request_t *r, return NGX_ERROR; } + if (rc == NGX_HTTP_UPSTREAM_RETRY) { + return NGX_ERROR; + } + if (rc == NGX_OK) { continue; } @@ -2535,6 +2554,7 @@ ngx_http_proxy_v2_parse_header(ngx_http_request_t *r, if (ctx->type == NGX_HTTP_V2_HEADERS_FRAME) { ctx->parsing_headers = 1; ctx->fragment_state = 0; + ctx->header_limit = r->upstream->conf->buffer_size; min = (ctx->flags & NGX_HTTP_V2_PADDED_FLAG ? 1 : 0) + (ctx->flags & NGX_HTTP_V2_PRIORITY_FLAG ? 5 : 0); @@ -2730,7 +2750,7 @@ ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, ngx_http_proxy_v2_ctx_t *ctx, ngx_buf_t *b) { u_char ch, *p, *last; - size_t size; + size_t len, size; ngx_uint_t index, size_update; enum { sw_start = 0, @@ -3067,6 +3087,14 @@ ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, break; case sw_name: + if (ctx->field_length > r->upstream->conf->buffer_size) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 " + "header name length: %uz", + ctx->field_length); + return NGX_ERROR; + } + ctx->name.len = ctx->field_huffman ? ctx->field_length * 8 / 5 : ctx->field_length; @@ -3176,6 +3204,14 @@ ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, break; case sw_value: + if (ctx->field_length > r->upstream->conf->buffer_size) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 " + "header value length: %uz", + ctx->field_length); + return NGX_ERROR; + } + ctx->value.len = ctx->field_huffman ? ctx->field_length * 8 / 5 : ctx->field_length; @@ -3272,6 +3308,16 @@ ngx_http_proxy_v2_parse_fragment(ngx_http_request_t *r, } } + len = ctx->name.len + ctx->value.len; + + if (len > ctx->header_limit) { + ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, + "upstream sent too large http2 header"); + return NGX_ERROR; + } + + ctx->header_limit -= len; + return NGX_OK; } @@ -3695,7 +3741,7 @@ ngx_http_proxy_v2_parse_settings(ngx_http_request_t *r, return NGX_ERROR; } - if (ctx->free == NULL && ctx->settings++ > 1000) { + if (ctx->settings++ > 1000) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent too many settings frames"); return NGX_ERROR; @@ -3853,7 +3899,7 @@ ngx_http_proxy_v2_parse_ping(ngx_http_request_t *r, return NGX_ERROR; } - if (ctx->free == NULL && ctx->pings++ > 1000) { + if (ctx->pings++ > 1000) { ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "upstream sent too many ping frames"); return NGX_ERROR; diff --git a/src/http/ngx_http_upstream.c b/src/http/ngx_http_upstream.c index bb8964add..9c2948f9f 100644 --- a/src/http/ngx_http_upstream.c +++ b/src/http/ngx_http_upstream.c @@ -2597,6 +2597,11 @@ done: return; } + if (rc == NGX_HTTP_UPSTREAM_RETRY) { + ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_RETRY); + return; + } + if (rc == NGX_ERROR) { ngx_http_upstream_finalize_request(r, u, NGX_HTTP_INTERNAL_SERVER_ERROR); @@ -4598,7 +4603,8 @@ ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u, } if (ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_403 - || ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404) + || ft_type == NGX_HTTP_UPSTREAM_FT_HTTP_404 + || ft_type == NGX_HTTP_UPSTREAM_FT_RETRY) { state = NGX_PEER_NEXT; @@ -4619,7 +4625,10 @@ ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u, "upstream timed out"); } - if (u->peer.cached && ft_type == NGX_HTTP_UPSTREAM_FT_ERROR) { + if (u->peer.cached + && (ft_type == NGX_HTTP_UPSTREAM_FT_ERROR + || ft_type == NGX_HTTP_UPSTREAM_FT_RETRY)) + { /* TODO: inform balancer instead */ u->peer.tries++; } @@ -4670,14 +4679,16 @@ ngx_http_upstream_next(ngx_http_request_t *r, ngx_http_upstream_t *u, timeout = u->conf->next_upstream_timeout; - if (u->request_sent + if (ft_type != NGX_HTTP_UPSTREAM_FT_RETRY + && u->request_sent && (r->method & (NGX_HTTP_POST|NGX_HTTP_LOCK|NGX_HTTP_PATCH))) { ft_type |= NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT; } if (u->peer.tries == 0 - || ((u->conf->next_upstream & ft_type) != ft_type) + || (ft_type != NGX_HTTP_UPSTREAM_FT_RETRY + && ((u->conf->next_upstream & ft_type) != ft_type)) || (u->request_sent && r->request_body_no_buffering) || (timeout && ngx_current_msec - u->peer.start_time >= timeout)) { diff --git a/src/http/ngx_http_upstream.h b/src/http/ngx_http_upstream.h index 4560bbe9a..9603b89eb 100644 --- a/src/http/ngx_http_upstream.h +++ b/src/http/ngx_http_upstream.h @@ -31,6 +31,7 @@ #define NGX_HTTP_UPSTREAM_FT_BUSY_LOCK 0x00001000 #define NGX_HTTP_UPSTREAM_FT_MAX_WAITING 0x00002000 #define NGX_HTTP_UPSTREAM_FT_NON_IDEMPOTENT 0x00004000 +#define NGX_HTTP_UPSTREAM_FT_RETRY 0x00008000 #define NGX_HTTP_UPSTREAM_FT_NOLIVE 0x40000000 #define NGX_HTTP_UPSTREAM_FT_OFF 0x80000000 @@ -44,6 +45,7 @@ #define NGX_HTTP_UPSTREAM_INVALID_HEADER 40 #define NGX_HTTP_UPSTREAM_EARLY_HINTS 41 +#define NGX_HTTP_UPSTREAM_RETRY 42 #define NGX_HTTP_UPSTREAM_IGN_XA_REDIRECT 0x00000002 diff --git a/src/http/v2/ngx_http_v2.c b/src/http/v2/ngx_http_v2.c index 7b1efa3ec..18e2c4321 100644 --- a/src/http/v2/ngx_http_v2.c +++ b/src/http/v2/ngx_http_v2.c @@ -2181,6 +2181,9 @@ ngx_http_v2_state_settings(ngx_http_v2_connection_t *h2c, u_char *pos, return ngx_http_v2_connection_error(h2c, NGX_HTTP_V2_SIZE_ERROR); } + h2c->state.init_window_set = 0; + h2c->state.max_frame_set = 0; + return ngx_http_v2_state_settings_params(h2c, pos, end); } @@ -2193,8 +2196,6 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, ngx_uint_t id, value; ngx_http_v2_out_frame_t *frame; - window_delta = 0; - while (h2c->state.length) { if (end - pos < NGX_HTTP_V2_SETTINGS_PARAM_SIZE) { return ngx_http_v2_state_save(h2c, pos, end, @@ -2222,7 +2223,8 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, NGX_HTTP_V2_FLOW_CTRL_ERROR); } - window_delta = value - h2c->init_window; + h2c->state.init_window_size = value; + h2c->state.init_window_set = 1; break; case NGX_HTTP_V2_MAX_FRAME_SIZE_SETTING: @@ -2238,7 +2240,8 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, NGX_HTTP_V2_PROTOCOL_ERROR); } - h2c->frame_size = value; + h2c->state.max_frame_size = value; + h2c->state.max_frame_set = 1; break; case NGX_HTTP_V2_ENABLE_PUSH_SETTING: @@ -2266,6 +2269,27 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, pos += NGX_HTTP_V2_SETTINGS_PARAM_SIZE; } + if (h2c->state.max_frame_set) { + h2c->frame_size = h2c->state.max_frame_size; + h2c->state.max_frame_set = 0; + } + + if (h2c->state.init_window_set) { + window_delta = (ssize_t) h2c->state.init_window_size + - (ssize_t) h2c->init_window; + + h2c->init_window = h2c->state.init_window_size; + + if (window_delta + && ngx_http_v2_adjust_windows(h2c, window_delta) != NGX_OK) + { + return ngx_http_v2_connection_error(h2c, + NGX_HTTP_V2_INTERNAL_ERROR); + } + + h2c->state.init_window_set = 0; + } + frame = ngx_http_v2_get_frame(h2c, NGX_HTTP_V2_SETTINGS_ACK_SIZE, NGX_HTTP_V2_SETTINGS_FRAME, NGX_HTTP_V2_ACK_FLAG, 0); @@ -2275,15 +2299,6 @@ ngx_http_v2_state_settings_params(ngx_http_v2_connection_t *h2c, u_char *pos, ngx_http_v2_queue_ordered_frame(h2c, frame); - if (window_delta) { - h2c->init_window += window_delta; - - if (ngx_http_v2_adjust_windows(h2c, window_delta) != NGX_OK) { - return ngx_http_v2_connection_error(h2c, - NGX_HTTP_V2_INTERNAL_ERROR); - } - } - return ngx_http_v2_state_complete(h2c, pos, end); } diff --git a/src/http/v2/ngx_http_v2.h b/src/http/v2/ngx_http_v2.h index 9605c8a23..2f9b25df9 100644 --- a/src/http/v2/ngx_http_v2.h +++ b/src/http/v2/ngx_http_v2.h @@ -80,10 +80,14 @@ typedef struct { ngx_uint_t sid; size_t length; size_t padding; + size_t init_window_size; + size_t max_frame_size; unsigned flags:8; unsigned incomplete:1; unsigned keep_pool:1; + unsigned init_window_set:1; + unsigned max_frame_set:1; /* HPACK */ unsigned parse_name:1;