diff options
Diffstat (limited to 'src/mux_h2.c')
-rw-r--r-- | src/mux_h2.c | 398 |
1 files changed, 322 insertions, 76 deletions
diff --git a/src/mux_h2.c b/src/mux_h2.c index 273e1f5..c28c5e1 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -78,11 +78,13 @@ struct h2c { int timeout; /* idle timeout duration in ticks */ int shut_timeout; /* idle timeout duration in ticks after GOAWAY was sent */ int idle_start; /* date of the last time the connection went idle (no stream + empty mbuf), or the start of current http req */ - /* 32-bit hole here */ + unsigned int nb_streams; /* number of streams in the tree */ unsigned int nb_sc; /* number of attached stream connectors */ unsigned int nb_reserved; /* number of reserved streams */ unsigned int stream_cnt; /* total number of streams seen */ + int glitches; /* total number of glitches on this connection */ + struct proxy *proxy; /* the proxy this connection was created for */ struct task *task; /* timeout management task */ struct h2_counters *px_counters; /* h2 counters attached to proxy */ @@ -304,7 +306,7 @@ enum { H2_STATS_COUNT /* must be the last member of the enum */ }; -static struct name_desc h2_stats[] = { +static struct stat_col h2_stats[] = { [H2_ST_HEADERS_RCVD] = { .name = "h2_headers_rcvd", .desc = "Total number of received HEADERS frames" }, [H2_ST_DATA_RCVD] = { .name = "h2_data_rcvd", @@ -353,25 +355,67 @@ static struct h2_counters { long long total_streams; /* total number of streams */ } h2_counters; -static void h2_fill_stats(void *data, struct field *stats) +static int h2_fill_stats(void *data, struct field *stats, unsigned int *selected_field) { struct h2_counters *counters = data; + unsigned int current_field = (selected_field != NULL ? *selected_field : 0); - stats[H2_ST_HEADERS_RCVD] = mkf_u64(FN_COUNTER, counters->headers_rcvd); - stats[H2_ST_DATA_RCVD] = mkf_u64(FN_COUNTER, counters->data_rcvd); - stats[H2_ST_SETTINGS_RCVD] = mkf_u64(FN_COUNTER, counters->settings_rcvd); - stats[H2_ST_RST_STREAM_RCVD] = mkf_u64(FN_COUNTER, counters->rst_stream_rcvd); - stats[H2_ST_GOAWAY_RCVD] = mkf_u64(FN_COUNTER, counters->goaway_rcvd); - - stats[H2_ST_CONN_PROTO_ERR] = mkf_u64(FN_COUNTER, counters->conn_proto_err); - stats[H2_ST_STRM_PROTO_ERR] = mkf_u64(FN_COUNTER, counters->strm_proto_err); - stats[H2_ST_RST_STREAM_RESP] = mkf_u64(FN_COUNTER, counters->rst_stream_resp); - stats[H2_ST_GOAWAY_RESP] = mkf_u64(FN_COUNTER, counters->goaway_resp); - - stats[H2_ST_OPEN_CONN] = mkf_u64(FN_GAUGE, counters->open_conns); - stats[H2_ST_OPEN_STREAM] = mkf_u64(FN_GAUGE, counters->open_streams); - stats[H2_ST_TOTAL_CONN] = mkf_u64(FN_COUNTER, counters->total_conns); - stats[H2_ST_TOTAL_STREAM] = mkf_u64(FN_COUNTER, counters->total_streams); + for (; current_field < H2_STATS_COUNT; current_field++) { + struct field metric = { 0 }; + + switch (current_field) { + case H2_ST_HEADERS_RCVD: + metric = mkf_u64(FN_COUNTER, counters->headers_rcvd); + break; + case H2_ST_DATA_RCVD: + metric = mkf_u64(FN_COUNTER, counters->data_rcvd); + break; + case H2_ST_SETTINGS_RCVD: + metric = mkf_u64(FN_COUNTER, counters->settings_rcvd); + break; + case H2_ST_RST_STREAM_RCVD: + metric = mkf_u64(FN_COUNTER, counters->rst_stream_rcvd); + break; + case H2_ST_GOAWAY_RCVD: + metric = mkf_u64(FN_COUNTER, counters->goaway_rcvd); + break; + case H2_ST_CONN_PROTO_ERR: + metric = mkf_u64(FN_COUNTER, counters->conn_proto_err); + break; + case H2_ST_STRM_PROTO_ERR: + metric = mkf_u64(FN_COUNTER, counters->strm_proto_err); + break; + case H2_ST_RST_STREAM_RESP: + metric = mkf_u64(FN_COUNTER, counters->rst_stream_resp); + break; + case H2_ST_GOAWAY_RESP: + metric = mkf_u64(FN_COUNTER, counters->goaway_resp); + break; + case H2_ST_OPEN_CONN: + metric = mkf_u64(FN_GAUGE, counters->open_conns); + break; + case H2_ST_OPEN_STREAM: + metric = mkf_u64(FN_GAUGE, counters->open_streams); + break; + case H2_ST_TOTAL_CONN: + metric = mkf_u64(FN_COUNTER, counters->total_conns); + break; + case H2_ST_TOTAL_STREAM: + metric = mkf_u64(FN_COUNTER, counters->total_streams); + break; + default: + /* not used for frontends. If a specific metric + * is requested, return an error. Otherwise continue. + */ + if (selected_field != NULL) + return 0; + continue; + } + stats[current_field] = metric; + if (selected_field != NULL) + break; + } + return 1; } static struct stats_module h2_stats_module = { @@ -408,6 +452,8 @@ static int h2_settings_header_table_size = 4096; /* initial value */ static int h2_settings_initial_window_size = 65536; /* default initial value */ static int h2_be_settings_initial_window_size = 0; /* backend's default initial value */ static int h2_fe_settings_initial_window_size = 0; /* frontend's default initial value */ +static int h2_be_glitches_threshold = 0; /* backend's max glitches: unlimited */ +static int h2_fe_glitches_threshold = 0; /* frontend's max glitches: unlimited */ static unsigned int h2_settings_max_concurrent_streams = 100; /* default value */ static unsigned int h2_be_settings_max_concurrent_streams = 0; /* backend value */ static unsigned int h2_fe_settings_max_concurrent_streams = 0; /* frontend value */ @@ -510,6 +556,9 @@ static void h2_trace(enum trace_level level, uint64_t mask, const struct trace_s if (h2c->errcode) chunk_appendf(&trace_buf, " err=%s/%02x", h2_err_str(h2c->errcode), h2c->errcode); + if (h2c->glitches) + chunk_appendf(&trace_buf, " glitches=%d", h2c->glitches); + if (h2c->flags & H2_CF_DEM_IN_PROGRESS && // frame processing has started, type and length are valid (mask & (H2_EV_RX_FRAME|H2_EV_RX_FHDR)) == (H2_EV_RX_FRAME|H2_EV_RX_FHDR)) { chunk_appendf(&trace_buf, " dft=%s/%02x dfl=%d", h2_ft_str(h2c->dft), h2c->dff, h2c->dfl); @@ -599,7 +648,6 @@ static inline int h2c_max_concurrent_streams(const struct h2c *h2c) return ret; } - /* update h2c timeout if needed */ static void h2c_update_timeout(struct h2c *h2c) { @@ -764,13 +812,13 @@ static int h2_buf_available(void *target) struct h2c *h2c = target; struct h2s *h2s; - if ((h2c->flags & H2_CF_DEM_DALLOC) && b_alloc(&h2c->dbuf)) { + if ((h2c->flags & H2_CF_DEM_DALLOC) && b_alloc(&h2c->dbuf, DB_MUX_RX)) { h2c->flags &= ~H2_CF_DEM_DALLOC; h2c_restart_reading(h2c, 1); return 1; } - if ((h2c->flags & H2_CF_MUX_MALLOC) && b_alloc(br_tail(h2c->mbuf))) { + if ((h2c->flags & H2_CF_MUX_MALLOC) && b_alloc(br_tail(h2c->mbuf), DB_MUX_TX)) { h2c->flags &= ~H2_CF_MUX_MALLOC; if (h2c->flags & H2_CF_DEM_MROOM) { @@ -782,7 +830,7 @@ static int h2_buf_available(void *target) if ((h2c->flags & H2_CF_DEM_SALLOC) && (h2s = h2c_st_by_id(h2c, h2c->dsi)) && h2s_sc(h2s) && - b_alloc(&h2s->rxbuf)) { + b_alloc(&h2s->rxbuf, DB_SE_RX)) { h2c->flags &= ~H2_CF_DEM_SALLOC; h2c_restart_reading(h2c, 1); return 1; @@ -796,10 +844,8 @@ static inline struct buffer *h2_get_buf(struct h2c *h2c, struct buffer *bptr) struct buffer *buf = NULL; if (likely(!LIST_INLIST(&h2c->buf_wait.list)) && - unlikely((buf = b_alloc(bptr)) == NULL)) { - h2c->buf_wait.target = h2c; - h2c->buf_wait.wakeup_cb = h2_buf_available; - LIST_APPEND(&th_ctx->buffer_wq, &h2c->buf_wait.list); + unlikely((buf = b_alloc(bptr, DB_MUX_RX)) == NULL)) { + b_queue(DB_MUX_RX, &h2c->buf_wait, h2c, h2_buf_available); } return buf; } @@ -1046,6 +1092,7 @@ static int h2_init(struct connection *conn, struct proxy *prx, struct session *s h2c->nb_sc = 0; h2c->nb_reserved = 0; h2c->stream_cnt = 0; + h2c->glitches = 0; h2c->dbuf = *input; h2c->dsi = -1; @@ -1146,8 +1193,7 @@ static void h2_release(struct h2c *h2c) hpack_dht_free(h2c->ddht); - if (LIST_INLIST(&h2c->buf_wait.list)) - LIST_DEL_INIT(&h2c->buf_wait.list); + b_dequeue(&h2c->buf_wait); h2_release_buf(h2c, &h2c->dbuf); h2_release_mbuf(h2c); @@ -1215,6 +1261,20 @@ static inline int h2s_mws(const struct h2s *h2s) return h2s->sws + h2s->h2c->miw; } +/* Returns 1 if the H2 error of the opposite side is forwardable to the peer. + * Otherwise 0 is returned. + * For now, only CANCEL from the client is forwardable to the server. + */ +static inline int h2s_is_forwardable_abort(struct h2s *h2s, struct se_abort_info *reason) +{ + enum h2_err err = H2_ERR_NO_ERROR; + + if (reason && ((reason->info & SE_ABRT_SRC_MASK) >> SE_ABRT_SRC_SHIFT) == SE_ABRT_SRC_MUX_H2) + err = reason->code; + + return ((h2s->h2c->flags & H2_CF_IS_BACK) && (err == H2_ERR_CANCEL)); +} + /* marks an error on the connection. Before settings are sent, we must not send * a GOAWAY frame, and the error state will prevent h2c_send_goaway_error() * from verifying this so we set H2_CF_GOAWAY_FAILED to make sure it will not @@ -1297,6 +1357,25 @@ static void __maybe_unused h2s_alert(struct h2s *h2s) TRACE_LEAVE(H2_EV_H2S_WAKE, h2s->h2c->conn, h2s); } +/* report one or more glitches on the connection. That is any unexpected event + * that may occasionally happen but if repeated a bit too much, might indicate + * a misbehaving or completely bogus peer. It normally returns zero, unless the + * glitch limit was reached, in which case an error is also reported on the + * connection. + */ +static inline int h2c_report_glitch(struct h2c *h2c, int increment) +{ + int thres = (h2c->flags & H2_CF_IS_BACK) ? + h2_be_glitches_threshold : h2_fe_glitches_threshold; + + h2c->glitches += increment; + if (thres && h2c->glitches >= thres) { + h2c_error(h2c, H2_ERR_ENHANCE_YOUR_CALM); + return 1; + } + return 0; +} + /* writes the 24-bit frame size <len> at address <frame> */ static inline __maybe_unused void h2_set_frame_size(void *frame, uint32_t len) { @@ -1586,6 +1665,7 @@ static struct h2s *h2c_frt_stream_new(struct h2c *h2c, int id, struct buffer *in BUG_ON(conn_reverse_in_preconnect(h2c->conn)); if (h2c->nb_streams >= h2c_max_concurrent_streams(h2c)) { + h2c_report_glitch(h2c, 1); TRACE_ERROR("HEADERS frame causing MAX_CONCURRENT_STREAMS to be exceeded", H2_EV_H2S_NEW|H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn); session_inc_http_req_ctr(sess); session_inc_http_err_ctr(sess); @@ -1835,6 +1915,7 @@ static int h2c_frt_recv_preface(struct h2c *h2c) if (!ret1) h2c->flags |= H2_CF_DEM_SHORT_READ; if (ret1 < 0 || (h2c->flags & H2_CF_RCVD_SHUT)) { + h2c_report_glitch(h2c, 1); TRACE_ERROR("I/O error or short read", H2_EV_RX_FRAME|H2_EV_RX_PREFACE, h2c->conn); h2c_error(h2c, H2_ERR_PROTOCOL_ERROR); if (b_data(&h2c->dbuf) || @@ -2298,12 +2379,26 @@ static int h2c_handle_settings(struct h2c *h2c) */ if (arg < 0) { // RFC7540#6.5.2 error = H2_ERR_FLOW_CONTROL_ERROR; + h2c_report_glitch(h2c, 1); goto fail; } + /* Let's count a glitch here in case of a reduction + * after H2_CS_SETTINGS1 because while it's not + * fundamentally invalid from a protocol's perspective, + * it's often suspicious. + */ + if (h2c->st0 != H2_CS_SETTINGS1 && arg < h2c->miw) + if (h2c_report_glitch(h2c, 1)) { + error = H2_ERR_ENHANCE_YOUR_CALM; + TRACE_STATE("glitch limit reached on SETTINGS frame", H2_EV_RX_FRAME|H2_EV_RX_SETTINGS|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn); + goto fail; + } + h2c->miw = arg; break; case H2_SETTINGS_MAX_FRAME_SIZE: if (arg < 16384 || arg > 16777215) { // RFC7540#6.5.2 + h2c_report_glitch(h2c, 1); TRACE_ERROR("MAX_FRAME_SIZE out of range", H2_EV_RX_FRAME|H2_EV_RX_SETTINGS, h2c->conn); error = H2_ERR_PROTOCOL_ERROR; HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err); @@ -2316,6 +2411,7 @@ static int h2c_handle_settings(struct h2c *h2c) break; case H2_SETTINGS_ENABLE_PUSH: if (arg < 0 || arg > 1) { // RFC7540#6.5.2 + h2c_report_glitch(h2c, 1); TRACE_ERROR("ENABLE_PUSH out of range", H2_EV_RX_FRAME|H2_EV_RX_SETTINGS, h2c->conn); error = H2_ERR_PROTOCOL_ERROR; HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err); @@ -2578,13 +2674,22 @@ static int h2c_handle_window_update(struct h2c *h2c, struct h2s *h2s) goto done; if (!inc) { + h2c_report_glitch(h2c, 1); TRACE_ERROR("stream WINDOW_UPDATE inc=0", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn, h2s); error = H2_ERR_PROTOCOL_ERROR; HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err); goto strm_err; } + /* WT: it would be tempting to count a glitch here for very small + * increments (less than a few tens of bytes), but that might be + * perfectly valid for many short streams, so better instead + * count the number of WU per frame maybe. That would be better + * dealt with using scores per frame. + */ + if (h2s_mws(h2s) >= 0 && h2s_mws(h2s) + inc < 0) { + h2c_report_glitch(h2c, 1); TRACE_ERROR("stream WINDOW_UPDATE inc<0", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn, h2s); error = H2_ERR_FLOW_CONTROL_ERROR; HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err); @@ -2603,6 +2708,7 @@ static int h2c_handle_window_update(struct h2c *h2c, struct h2s *h2s) else { /* connection window update */ if (!inc) { + h2c_report_glitch(h2c, 1); TRACE_ERROR("conn WINDOW_UPDATE inc=0", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn); error = H2_ERR_PROTOCOL_ERROR; HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err); @@ -2610,6 +2716,7 @@ static int h2c_handle_window_update(struct h2c *h2c, struct h2s *h2s) } if (h2c->mws >= 0 && h2c->mws + inc < 0) { + h2c_report_glitch(h2c, 1); TRACE_ERROR("conn WINDOW_UPDATE inc<0", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn); error = H2_ERR_FLOW_CONTROL_ERROR; goto conn_err; @@ -2679,6 +2786,7 @@ static int h2c_handle_priority(struct h2c *h2c) if (h2_get_n32(&h2c->dbuf, 0) == h2c->dsi) { /* 7540#5.3 : can't depend on itself */ + h2c_report_glitch(h2c, 1); TRACE_ERROR("PRIORITY depends on itself", H2_EV_RX_FRAME|H2_EV_RX_WU, h2c->conn); h2c_error(h2c, H2_ERR_PROTOCOL_ERROR); HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err); @@ -2715,6 +2823,10 @@ static int h2c_handle_rst_stream(struct h2c *h2c, struct h2s *h2s) if (h2s_sc(h2s)) { se_fl_set_error(h2s->sd); + if (!h2s->sd->abort_info.info) { + h2s->sd->abort_info.info = (SE_ABRT_SRC_MUX_H2 << SE_ABRT_SRC_SHIFT); + h2s->sd->abort_info.code = h2s->errcode; + } h2s_alert(h2s); } @@ -2792,6 +2904,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s) else if (h2c->dsi <= h2c->max_id || !(h2c->dsi & 1)) { /* RFC7540#5.1.1 stream id > prev ones, and must be odd here */ error = H2_ERR_PROTOCOL_ERROR; + h2c_report_glitch(h2c, 1); TRACE_ERROR("HEADERS on invalid stream ID", H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn); HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err); sess_log(h2c->conn->owner); @@ -2809,6 +2922,7 @@ static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s) * stop processing its requests for real. */ error = H2_ERR_ENHANCE_YOUR_CALM; + h2c_report_glitch(h2c, 1); TRACE_STATE("Stream limit violated", H2_EV_STRM_SHUT, h2c->conn); HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err); sess_log(h2c->conn->owner); @@ -2977,6 +3091,7 @@ static struct h2s *h2c_bck_handle_headers(struct h2c *h2c, struct h2s *h2s) if (h2s->st != H2_SS_OPEN && h2s->st != H2_SS_HLOC) { /* RFC7540#5.1 */ + h2c_report_glitch(h2c, 1); TRACE_ERROR("response HEADERS in invalid state", H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn, h2s); h2s_error(h2s, H2_ERR_STREAM_CLOSED); h2c->st0 = H2_CS_FRAME_E; @@ -3067,22 +3182,24 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s) if (h2s->st != H2_SS_OPEN && h2s->st != H2_SS_HLOC) { /* RFC7540#6.1 */ error = H2_ERR_STREAM_CLOSED; - goto strm_err; + goto strm_err_wu; } if (!(h2s->flags & H2_SF_HEADERS_RCVD)) { /* RFC9113#8.1: The header section must be received before the message content */ + h2c_report_glitch(h2c, 1); TRACE_ERROR("Unexpected DATA frame before the message headers", H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s); error = H2_ERR_PROTOCOL_ERROR; HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err); - goto strm_err; + goto strm_err_wu; } if ((h2s->flags & H2_SF_DATA_CLEN) && (h2c->dfl - h2c->dpl) > h2s->body_len) { /* RFC7540#8.1.2 */ + h2c_report_glitch(h2c, 1); TRACE_ERROR("DATA frame larger than content-length", H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s); error = H2_ERR_PROTOCOL_ERROR; HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err); - goto strm_err; + goto strm_err_wu; } if (!(h2c->flags & H2_CF_IS_BACK) && (h2s->flags & (H2_SF_TUNNEL_ABRT|H2_SF_ES_SENT)) == (H2_SF_TUNNEL_ABRT|H2_SF_ES_SENT) && @@ -3095,7 +3212,7 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s) */ TRACE_ERROR("Request DATA frame for aborted tunnel", H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s); error = H2_ERR_CANCEL; - goto strm_err; + goto strm_err_wu; } if (!h2_frt_transfer_data(h2s)) @@ -3137,6 +3254,7 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s) if (h2s->flags & H2_SF_DATA_CLEN && h2s->body_len) { /* RFC7540#8.1.2 */ + h2c_report_glitch(h2c, 1); TRACE_ERROR("ES on DATA frame before content-length", H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s); error = H2_ERR_PROTOCOL_ERROR; HA_ATOMIC_INC(&h2c->px_counters->strm_proto_err); @@ -3156,6 +3274,12 @@ static int h2c_handle_data(struct h2c *h2c, struct h2s *h2s) TRACE_LEAVE(H2_EV_RX_FRAME|H2_EV_RX_DATA, h2c->conn, h2s); return 1; + strm_err_wu: + /* stream error before the frame was taken into account, we're + * going to kill the stream but must still update the connection's + * window. + */ + h2c->rcvd_c += h2c->dfl - h2c->dpl; strm_err: h2s_error(h2s, error); h2c->st0 = H2_CS_FRAME_E; @@ -3179,6 +3303,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s) /* RFC7540#5.1: any frame other than HEADERS or PRIORITY in * this state MUST be treated as a connection error */ + h2c_report_glitch(h2c, 1); TRACE_ERROR("invalid frame type for IDLE state", H2_EV_RX_FRAME|H2_EV_RX_FHDR, h2c->conn, h2s); h2c_error(h2c, H2_ERR_PROTOCOL_ERROR); if (!h2c->nb_streams && !(h2c->flags & H2_CF_IS_BACK)) { @@ -3192,6 +3317,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s) if (h2s->st == H2_SS_IDLE && (h2c->flags & H2_CF_IS_BACK)) { /* only PUSH_PROMISE would be permitted here */ + h2c_report_glitch(h2c, 1); TRACE_ERROR("invalid frame type for IDLE state (back)", H2_EV_RX_FRAME|H2_EV_RX_FHDR, h2c->conn, h2s); h2c_error(h2c, H2_ERR_PROTOCOL_ERROR); HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err); @@ -3207,11 +3333,13 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s) * PUSH_PROMISE/CONTINUATION cause connection errors. */ if (h2_ft_bit(h2c->dft) & H2_FT_HDR_MASK) { + h2c_report_glitch(h2c, 1); TRACE_ERROR("invalid frame type for HREM state", H2_EV_RX_FRAME|H2_EV_RX_FHDR, h2c->conn, h2s); h2c_error(h2c, H2_ERR_PROTOCOL_ERROR); HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err); } else { + h2c_report_glitch(h2c, 1); h2s_error(h2s, H2_ERR_STREAM_CLOSED); } TRACE_DEVEL("leaving in error (hrem&!wu&!rst&!prio)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s); @@ -3241,6 +3369,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s) * receives an unexpected stream identifier * MUST respond with a connection error. */ + h2c_report_glitch(h2c, 1); h2c_error(h2c, H2_ERR_STREAM_CLOSED); TRACE_DEVEL("leaving in error (closed&hdrmask)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s); return 0; @@ -3267,6 +3396,14 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s) * RST_RCVD bit, we don't want to accidentally catch valid * frames for a closed stream, i.e. RST/PRIO/WU. */ + if (h2c->dft == H2_FT_DATA) { + /* even if we reject out-of-stream DATA, it must + * still count against the connection's flow control. + */ + h2c->rcvd_c += h2c->dfl - h2c->dpl; + } + + h2c_report_glitch(h2c, 1); h2s_error(h2s, H2_ERR_STREAM_CLOSED); h2c->st0 = H2_CS_FRAME_E; TRACE_DEVEL("leaving in error (rst_rcvd&!hdrmask)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s); @@ -3291,6 +3428,7 @@ static int h2_frame_check_vs_state(struct h2c *h2c, struct h2s *h2s) if (h2c->dft != H2_FT_RST_STREAM && h2c->dft != H2_FT_PRIORITY && h2c->dft != H2_FT_WINDOW_UPDATE) { + h2c_report_glitch(h2c, 1); h2c_error(h2c, H2_ERR_STREAM_CLOSED); TRACE_DEVEL("leaving in error (rst_sent&!rst&!prio&!wu)", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn, h2s); return 0; @@ -3426,6 +3564,10 @@ static void h2_process_demux(struct h2c *h2c) if (unlikely(h2c_frt_recv_preface(h2c) <= 0)) { /* RFC7540#3.5: a GOAWAY frame MAY be omitted */ if (h2c->st0 == H2_CS_ERROR) { + if (b_data(&h2c->dbuf) || + !(((const struct session *)h2c->conn->owner)->fe->options & (PR_O_NULLNOLOG|PR_O_IGNORE_PRB))) + h2c_report_glitch(h2c, 1); + TRACE_PROTO("failed to receive preface", H2_EV_RX_PREFACE|H2_EV_PROTO_ERR, h2c->conn); h2c->st0 = H2_CS_ERROR2; if (b_data(&h2c->dbuf) || @@ -3450,6 +3592,7 @@ static void h2_process_demux(struct h2c *h2c) /* RFC7540#3.5: a GOAWAY frame MAY be omitted */ h2c->flags |= H2_CF_DEM_SHORT_READ; if (h2c->st0 == H2_CS_ERROR) { + h2c_report_glitch(h2c, 1); TRACE_ERROR("failed to receive settings", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_SETTINGS|H2_EV_PROTO_ERR, h2c->conn); h2c->st0 = H2_CS_ERROR2; if (!(h2c->flags & H2_CF_IS_BACK)) @@ -3460,6 +3603,7 @@ static void h2_process_demux(struct h2c *h2c) if (hdr.sid || hdr.ft != H2_FT_SETTINGS || hdr.ff & H2_F_SETTINGS_ACK) { /* RFC7540#3.5: a GOAWAY frame MAY be omitted */ + h2c_report_glitch(h2c, 1); TRACE_ERROR("unexpected frame type or flags", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_SETTINGS|H2_EV_PROTO_ERR, h2c->conn); h2c_error(h2c, H2_ERR_PROTOCOL_ERROR); h2c->st0 = H2_CS_ERROR2; @@ -3471,6 +3615,7 @@ static void h2_process_demux(struct h2c *h2c) if ((int)hdr.len < 0 || (int)hdr.len > global.tune.bufsize) { /* RFC7540#3.5: a GOAWAY frame MAY be omitted */ + h2c_report_glitch(h2c, 1); TRACE_ERROR("invalid settings frame length", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_SETTINGS|H2_EV_PROTO_ERR, h2c->conn); h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR); h2c->st0 = H2_CS_ERROR2; @@ -3512,6 +3657,7 @@ static void h2_process_demux(struct h2c *h2c) } if ((int)hdr.len < 0 || (int)hdr.len > global.tune.bufsize) { + h2c_report_glitch(h2c, 1); TRACE_ERROR("invalid H2 frame length", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn); h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR); if (!h2c->nb_streams && !(h2c->flags & H2_CF_IS_BACK)) { @@ -3542,6 +3688,7 @@ static void h2_process_demux(struct h2c *h2c) * padlen in the flow control, so it must be adjusted. */ if (hdr.len < 1) { + h2c_report_glitch(h2c, 1); TRACE_ERROR("invalid H2 padded frame length", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn); h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR); if (!(h2c->flags & H2_CF_IS_BACK)) @@ -3559,6 +3706,7 @@ static void h2_process_demux(struct h2c *h2c) padlen = *(uint8_t *)b_peek(&h2c->dbuf, 9); if (padlen > hdr.len) { + h2c_report_glitch(h2c, 1); TRACE_ERROR("invalid H2 padding length", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn); /* RFC7540#6.1 : pad length = length of * frame payload or greater => error. @@ -3591,6 +3739,7 @@ static void h2_process_demux(struct h2c *h2c) /* check for minimum basic frame format validity */ ret = h2_frame_check(h2c->dft, 1, h2c->dsi, h2c->dfl, global.tune.bufsize); if (ret != H2_ERR_NO_ERROR) { + h2c_report_glitch(h2c, 1); TRACE_ERROR("received invalid H2 frame header", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_PROTO_ERR, h2c->conn); h2c_error(h2c, ret); if (!(h2c->flags & H2_CF_IS_BACK)) @@ -3679,6 +3828,7 @@ static void h2_process_demux(struct h2c *h2c) * frames' parsers consume all following CONTINUATION * frames so this one is out of sequence. */ + h2c_report_glitch(h2c, 1); TRACE_ERROR("received unexpected H2 CONTINUATION frame", H2_EV_RX_FRAME|H2_EV_RX_CONT|H2_EV_H2C_ERR, h2c->conn, h2s); h2c_error(h2c, H2_ERR_PROTOCOL_ERROR); if (!(h2c->flags & H2_CF_IS_BACK)) @@ -4251,8 +4401,13 @@ static int h2_process(struct h2c *h2c) if (!(h2c->flags & H2_CF_DEM_BLOCK_ANY) && (b_data(&h2c->dbuf) || (h2c->flags & H2_CF_RCVD_SHUT))) { + int prev_glitches = h2c->glitches; + h2_process_demux(h2c); + if (h2c->glitches != prev_glitches && !(h2c->flags & H2_CF_IS_BACK)) + session_add_glitch_ctr(h2c->conn->owner, h2c->glitches - prev_glitches); + if (h2c->st0 >= H2_CS_ERROR || (h2c->flags & H2_CF_ERROR)) b_reset(&h2c->dbuf); @@ -4568,6 +4723,15 @@ static int h2_ctl(struct connection *conn, enum mux_ctl_type mux_ctl, void *outp tasklet_wakeup(h2c->wait_event.tasklet); return 0; + case MUX_CTL_GET_GLITCHES: + return h2c->glitches; + + case MUX_CTL_GET_NBSTRM: + return h2c->nb_streams; + + case MUX_CTL_GET_MAXSTRM: + return h2c->streams_limit; + default: return -1; } @@ -4676,6 +4840,10 @@ static void h2_detach(struct sedesc *sd) } } if (eb_is_empty(&h2c->streams_by_id)) { + /* mark that the tasklet may lose its context to another thread and + * that the handler needs to check it under the idle conns lock. + */ + HA_ATOMIC_OR(&h2c->wait_event.tasklet->state, TASK_F_USR1); if (session_check_idle_conn(h2c->conn->owner, h2c->conn) != 0) { /* At this point either the connection is destroyed, or it's been added to the server idle list, just stop */ TRACE_DEVEL("leaving without reusable idle connection", H2_EV_STRM_END); @@ -4715,7 +4883,7 @@ static void h2_detach(struct sedesc *sd) } else if (!h2c->conn->hash_node->node.node.leaf_p && h2_avail_streams(h2c->conn) > 0 && objt_server(h2c->conn->target) && - !LIST_INLIST(&h2c->conn->session_list)) { + !LIST_INLIST(&h2c->conn->sess_el)) { srv_add_to_avail_list(__objt_server(h2c->conn->target), h2c->conn); } } @@ -4741,7 +4909,7 @@ static void h2_detach(struct sedesc *sd) } /* Performs a synchronous or asynchronous shutr(). */ -static void h2_do_shutr(struct h2s *h2s) +static void h2_do_shutr(struct h2s *h2s, struct se_abort_info *reason) { struct h2c *h2c = h2s->h2c; @@ -4764,6 +4932,10 @@ static void h2_do_shutr(struct h2s *h2s) h2c_error(h2c, H2_ERR_ENHANCE_YOUR_CALM); h2s_error(h2s, H2_ERR_ENHANCE_YOUR_CALM); } + else if (h2s_is_forwardable_abort(h2s, reason)) { + TRACE_STATE("shutr using opposite endp code", H2_EV_STRM_SHUT, h2c->conn, h2s); + h2s_error(h2s, reason->code); + } else if (!(h2s->flags & H2_SF_HEADERS_SENT)) { /* Nothing was never sent for this stream, so reset with * REFUSED_STREAM error to let the client retry the @@ -4809,8 +4981,9 @@ add_to_list: return; } + /* Performs a synchronous or asynchronous shutw(). */ -static void h2_do_shutw(struct h2s *h2s) +static void h2_do_shutw(struct h2s *h2s, struct se_abort_info *reason) { struct h2c *h2c = h2s->h2c; @@ -4820,6 +4993,7 @@ static void h2_do_shutw(struct h2s *h2s) TRACE_ENTER(H2_EV_STRM_SHUT, h2c->conn, h2s); if (h2s->st != H2_SS_ERROR && + !h2s_is_forwardable_abort(h2s, reason) && (h2s->flags & (H2_SF_HEADERS_SENT | H2_SF_MORE_HTX_DATA)) == H2_SF_HEADERS_SENT) { /* we can cleanly close using an empty data frame only after headers * and if no more data is expected to be sent. @@ -4844,6 +5018,10 @@ static void h2_do_shutw(struct h2s *h2s) h2c_error(h2c, H2_ERR_ENHANCE_YOUR_CALM); h2s_error(h2s, H2_ERR_ENHANCE_YOUR_CALM); } + else if (h2s_is_forwardable_abort(h2s, reason)) { + TRACE_STATE("shutw using opposite endp code", H2_EV_STRM_SHUT, h2c->conn, h2s); + h2s_error(h2s, reason->code); + } else if (h2s->flags & H2_SF_MORE_HTX_DATA) { /* some unsent data were pending (e.g. abort during an upload), * let's send a CANCEL. @@ -4910,10 +5088,10 @@ struct task *h2_deferred_shut(struct task *t, void *ctx, unsigned int state) } if (h2s->flags & H2_SF_WANT_SHUTW) - h2_do_shutw(h2s); + h2_do_shutw(h2s, NULL); if (h2s->flags & H2_SF_WANT_SHUTR) - h2_do_shutr(h2s); + h2_do_shutr(h2s, NULL); if (!(h2s->flags & (H2_SF_WANT_SHUTR|H2_SF_WANT_SHUTW))) { /* We're done trying to send, remove ourself from the send_list */ @@ -4932,24 +5110,17 @@ struct task *h2_deferred_shut(struct task *t, void *ctx, unsigned int state) return t; } -/* shutr() called by the stream connector (mux_ops.shutr) */ -static void h2_shutr(struct stconn *sc, enum co_shr_mode mode) -{ - struct h2s *h2s = __sc_mux_strm(sc); - - TRACE_ENTER(H2_EV_STRM_SHUT, h2s->h2c->conn, h2s); - if (mode) - h2_do_shutr(h2s); - TRACE_LEAVE(H2_EV_STRM_SHUT, h2s->h2c->conn, h2s); -} - -/* shutw() called by the stream connector (mux_ops.shutw) */ -static void h2_shutw(struct stconn *sc, enum co_shw_mode mode) +static void h2_shut(struct stconn *sc, enum se_shut_mode mode, struct se_abort_info *reason) { struct h2s *h2s = __sc_mux_strm(sc); TRACE_ENTER(H2_EV_STRM_SHUT, h2s->h2c->conn, h2s); - h2_do_shutw(h2s); + if (mode & (SE_SHW_SILENT|SE_SHW_NORMAL)) { + /* Pass the reason for silent shutw only (abort) */ + h2_do_shutw(h2s, (mode & SE_SHW_SILENT) ? reason : NULL); + } + if (mode & SE_SHR_RESET) + h2_do_shutr(h2s, reason); TRACE_LEAVE(H2_EV_STRM_SHUT, h2s->h2c->conn, h2s); } @@ -5004,7 +5175,8 @@ static void h2_shutw(struct stconn *sc, enum co_shw_mode mode) * * The H2_SF_HEADERS_RCVD flag is also looked at in the <flags> field prior to * decoding, in order to detect if we're dealing with a headers or a trailers - * block (the trailers block appears after H2_SF_HEADERS_RCVD was seen). + * block (the trailers block appears after H2_SF_HEADERS_RCVD was seen). The + * function takes care of counting glitches. */ static int h2c_dec_hdrs(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags, unsigned long long *body_len, char *upgrade_protocol) { @@ -5014,7 +5186,8 @@ static int h2c_dec_hdrs(struct h2c *h2c, struct buffer *rxbuf, uint32_t *flags, struct buffer *copy = NULL; unsigned int msgf; struct htx *htx = NULL; - int flen; // header frame len + int flen = 0; // header frame len + int fragments = 0; int hole = 0; int ret = 0; int outlen; @@ -5048,6 +5221,7 @@ next_frame: if (hdr.ft != H2_FT_CONTINUATION) { /* RFC7540#6.10: frame of unexpected type */ + h2c_report_glitch(h2c, 1); TRACE_STATE("not continuation!", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_HDR|H2_EV_RX_CONT|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn); h2c_error(h2c, H2_ERR_PROTOCOL_ERROR); HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err); @@ -5056,6 +5230,7 @@ next_frame: if (hdr.sid != h2c->dsi) { /* RFC7540#6.10: frame of different stream */ + h2c_report_glitch(h2c, 1); TRACE_STATE("different stream ID!", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_HDR|H2_EV_RX_CONT|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn); h2c_error(h2c, H2_ERR_PROTOCOL_ERROR); HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err); @@ -5064,6 +5239,7 @@ next_frame: if ((unsigned)hdr.len > (unsigned)global.tune.bufsize) { /* RFC7540#4.2: invalid frame length */ + h2c_report_glitch(h2c, 1); TRACE_STATE("too large frame!", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_HDR|H2_EV_RX_CONT|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn); h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR); goto fail; @@ -5086,6 +5262,7 @@ next_frame: hole += h2c->dpl + 9; h2c->dpl = 0; TRACE_STATE("waiting for next continuation frame", H2_EV_RX_FRAME|H2_EV_RX_FHDR|H2_EV_RX_CONT|H2_EV_RX_HDR, h2c->conn); + fragments++; goto next_frame; } @@ -5109,6 +5286,7 @@ next_frame: if (h2c->dff & H2_F_HEADERS_PRIORITY) { if (read_n32(hdrs) == h2c->dsi) { /* RFC7540#5.3.1 : stream dep may not depend on itself */ + h2c_report_glitch(h2c, 1); TRACE_STATE("invalid stream dependency!", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn); h2c_error(h2c, H2_ERR_PROTOCOL_ERROR); HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err); @@ -5116,6 +5294,7 @@ next_frame: } if (flen < 5) { + h2c_report_glitch(h2c, 1); TRACE_STATE("frame too short for priority!", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn); h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR); goto fail; @@ -5167,6 +5346,7 @@ next_frame: } if (outlen < 0) { + h2c_report_glitch(h2c, 1); TRACE_STATE("failed to decompress HPACK", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn); h2c_error(h2c, H2_ERR_COMPRESSION_ERROR); goto fail; @@ -5199,6 +5379,7 @@ next_frame: if (outlen < 0 || htx_free_space(htx) < global.tune.maxrewrite) { /* too large headers? this is a stream error only */ + h2c_report_glitch(h2c, 1); TRACE_STATE("message headers too large or invalid", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2S_ERR|H2_EV_PROTO_ERR, h2c->conn); htx->flags |= HTX_FL_PARSING_ERROR; goto fail; @@ -5234,6 +5415,7 @@ next_frame: if (h2c->dff & H2_F_HEADERS_END_STREAM) { if (msgf & H2_MSGF_RSP_1XX) { /* RFC9113#8.1 : HEADERS frame with the ES flag set that carries an informational status code is malformed */ + h2c_report_glitch(h2c, 1); TRACE_STATE("invalid interim response with ES flag!", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn); goto fail; } @@ -5269,6 +5451,20 @@ next_frame: htx_to_buf(htx, rxbuf); free_trash_chunk(copy); TRACE_LEAVE(H2_EV_RX_FRAME|H2_EV_RX_HDR, h2c->conn); + + /* Check for abuse of CONTINUATION: more than 4 fragments and less than + * 1kB per fragment is clearly unusual and suspicious enough to count + * one glitch per 1kB fragment in a 16kB buffer, which means that an + * abuser sending 1600 1-byte frames in a 16kB buffer would increment + * its counter by 100. + */ + if (unlikely(fragments > 4) && fragments > flen / 1024 && ret != 0) { + if (h2c_report_glitch(h2c, (fragments + 15) / 16)) { + TRACE_STATE("glitch limit reached on CONTINUATION frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn); + ret = -1; + } + } + return ret; fail: @@ -5279,6 +5475,7 @@ next_frame: /* This is the last HEADERS frame hence a trailer */ if (!(h2c->dff & H2_F_HEADERS_END_STREAM)) { /* It's a trailer but it's missing ES flag */ + h2c_report_glitch(h2c, 1); TRACE_STATE("missing EH on trailers frame", H2_EV_RX_FRAME|H2_EV_RX_HDR|H2_EV_H2C_ERR|H2_EV_PROTO_ERR, h2c->conn); h2c_error(h2c, H2_ERR_PROTOCOL_ERROR); HA_ATOMIC_INC(&h2c->px_counters->conn_proto_err); @@ -6075,10 +6272,9 @@ static size_t h2s_snd_bhdrs(struct h2s *h2s, struct htx *htx) } /* Try to send a DATA frame matching HTTP response present in HTX structure - * present in <buf>, for stream <h2s>. Returns the number of bytes sent. The - * caller must check the stream's status to detect any error which might have - * happened subsequently to a successful send. Returns the number of data bytes - * consumed, or zero if nothing done. + * present in <buf>, for stream <h2s>. The caller must check the stream's status + * to detect any error which might have happened subsequently to a successful + * send. Returns the number of data bytes consumed, or zero if nothing done. */ static size_t h2s_make_data(struct h2s *h2s, struct buffer *buf, size_t count) { @@ -6973,7 +7169,7 @@ static size_t h2_snd_buf(struct stconn *sc, struct buffer *buf, size_t count, in return total; } -static size_t h2_nego_ff(struct stconn *sc, struct buffer *input, size_t count, unsigned int may_splice) +static size_t h2_nego_ff(struct stconn *sc, struct buffer *input, size_t count, unsigned int flags) { struct h2s *h2s = __sc_mux_strm(sc); struct h2c *h2c = h2s->h2c; @@ -7249,9 +7445,9 @@ static int h2_dump_h2c_info(struct buffer *msg, struct h2c *h2c, const char *pfx hmbuf = br_head(h2c->mbuf); tmbuf = br_tail(h2c->mbuf); chunk_appendf(msg, " h2c.st0=%s .err=%d .maxid=%d .lastid=%d .flg=0x%04x" - " .nbst=%u .nbsc=%u", + " .nbst=%u .nbsc=%u, .glitches=%d", h2c_st_to_str(h2c->st0), h2c->errcode, h2c->max_id, h2c->last_sid, h2c->flags, - h2c->nb_streams, h2c->nb_sc); + h2c->nb_streams, h2c->nb_sc, h2c->glitches); if (pfx) chunk_appendf(msg, "\n%s", pfx); @@ -7332,25 +7528,35 @@ static int h2_show_sd(struct buffer *msg, struct sedesc *sd, const char *pfx) * Return 0 if successful, non-zero otherwise. * Expected to be called with the old thread lock held. */ -static int h2_takeover(struct connection *conn, int orig_tid) +static int h2_takeover(struct connection *conn, int orig_tid, int release) { struct h2c *h2c = conn->ctx; struct task *task; - struct task *new_task; - struct tasklet *new_tasklet; + struct task *new_task = NULL; + struct tasklet *new_tasklet = NULL; /* Pre-allocate tasks so that we don't have to roll back after the xprt * has been migrated. */ - new_task = task_new_here(); - new_tasklet = tasklet_new(); - if (!new_task || !new_tasklet) - goto fail; + if (!release) { + /* If the connection is attached to a buffer_wait (extremely + * rare), it will be woken up at any instant by its own thread + * and we can't undo it anyway, so let's give up on this one. + * It's not interesting anyway since it's not usable right now. + */ + if (LIST_INLIST(&h2c->buf_wait.list)) + goto fail; + + new_task = task_new_here(); + new_tasklet = tasklet_new(); + if (!new_task || !new_tasklet) + goto fail; + } if (fd_takeover(conn->handle.fd, conn) != 0) goto fail; - if (conn->xprt->takeover && conn->xprt->takeover(conn, conn->xprt_ctx, orig_tid) != 0) { + if (conn->xprt->takeover && conn->xprt->takeover(conn, conn->xprt_ctx, orig_tid, release) != 0) { /* We failed to takeover the xprt, even if the connection may * still be valid, flag it as error'd, as we have already * taken over the fd, and wake the tasklet, so that it will @@ -7377,8 +7583,10 @@ static int h2_takeover(struct connection *conn, int orig_tid) h2c->task = new_task; new_task = NULL; - h2c->task->process = h2_timeout_task; - h2c->task->context = h2c; + if (!release) { + h2c->task->process = h2_timeout_task; + h2c->task->context = h2c; + } } /* To let the tasklet know it should free itself, and do nothing else, @@ -7388,10 +7596,26 @@ static int h2_takeover(struct connection *conn, int orig_tid) tasklet_wakeup_on(h2c->wait_event.tasklet, orig_tid); h2c->wait_event.tasklet = new_tasklet; - h2c->wait_event.tasklet->process = h2_io_cb; - h2c->wait_event.tasklet->context = h2c; - h2c->conn->xprt->subscribe(h2c->conn, h2c->conn->xprt_ctx, - SUB_RETRY_RECV, &h2c->wait_event); + if (!release) { + h2c->wait_event.tasklet->process = h2_io_cb; + h2c->wait_event.tasklet->context = h2c; + h2c->conn->xprt->subscribe(h2c->conn, h2c->conn->xprt_ctx, + SUB_RETRY_RECV, &h2c->wait_event); + } + + if (release) { + /* we're being called for a server deletion and are running + * under thread isolation. That's the only way we can + * unregister a possible subscription of the original + * connection from its owner thread's queue, as this involves + * manipulating thread-unsafe areas. Note that it is not + * possible to just call b_dequeue() here as it would update + * the current thread's bufq_map and not the original one. + */ + BUG_ON(!thread_isolated()); + if (LIST_INLIST(&h2c->buf_wait.list)) + _b_dequeue(&h2c->buf_wait, orig_tid); + } if (new_task) __task_free(new_task); @@ -7407,6 +7631,27 @@ static int h2_takeover(struct connection *conn, int orig_tid) /* functions below are dedicated to the config parsers */ /*******************************************************/ +/* config parser for global "tune.h2.{fe,be}.glitches-threshold" */ +static int h2_parse_glitches_threshold(char **args, int section_type, struct proxy *curpx, + const struct proxy *defpx, const char *file, int line, + char **err) +{ + int *vptr; + + if (too_many_args(1, args, err, NULL)) + return -1; + + /* backend/frontend */ + vptr = (args[0][8] == 'b') ? &h2_be_glitches_threshold : &h2_fe_glitches_threshold; + + *vptr = atoi(args[1]); + if (*vptr < 0) { + memprintf(err, "'%s' expects a positive numeric value.", args[0]); + return -1; + } + return 0; +} + /* config parser for global "tune.h2.header-table-size" */ static int h2_parse_header_table_size(char **args, int section_type, struct proxy *curpx, const struct proxy *defpx, const char *file, int line, @@ -7547,8 +7792,7 @@ static const struct mux_ops h2_ops = { .destroy = h2_destroy, .avail_streams = h2_avail_streams, .used_streams = h2_used_streams, - .shutr = h2_shutr, - .shutw = h2_shutw, + .shut = h2_shut, .ctl = h2_ctl, .sctl = h2_sctl, .show_fd = h2_show_fd, @@ -7565,8 +7809,10 @@ INITCALL1(STG_REGISTER, register_mux_proto, &mux_proto_h2); /* config keyword parsers */ static struct cfg_kw_list cfg_kws = {ILH, { + { CFG_GLOBAL, "tune.h2.be.glitches-threshold", h2_parse_glitches_threshold }, { CFG_GLOBAL, "tune.h2.be.initial-window-size", h2_parse_initial_window_size }, { CFG_GLOBAL, "tune.h2.be.max-concurrent-streams", h2_parse_max_concurrent_streams }, + { CFG_GLOBAL, "tune.h2.fe.glitches-threshold", h2_parse_glitches_threshold }, { CFG_GLOBAL, "tune.h2.fe.initial-window-size", h2_parse_initial_window_size }, { CFG_GLOBAL, "tune.h2.fe.max-concurrent-streams", h2_parse_max_concurrent_streams }, { CFG_GLOBAL, "tune.h2.fe.max-total-streams", h2_parse_max_total_streams }, |