From: Willy Tarreau Date: Tue, 16 May 2017 19:51:05 +0000 (+0200) Subject: MINOR: h2: add a function to send a GOAWAY error frame X-Git-Tag: v1.8-rc1~40 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=081d472f79398bd4fd2b4f7c80b3cf251b99b489;p=thirdparty%2Fhaproxy.git MINOR: h2: add a function to send a GOAWAY error frame For now it's only used to report immediate errors by announcing the highest known stream-id on the mux's error path. The function may be used both while processing a stream or directly in relation with the connection. The wake() callback will automatically ask for send access if an error is reported. The function should be usable for graceful shutdowns as well by simply setting h2c->last_sid to the highest acceptable stream-id (2^31-1) prior to calling the function. A connection flag (H2_CF_GOAWAY_SENT) is set once the frame was successfully sent. It will be usable to detect when it's safe to close the connection. Another flag (H2_CF_GOAWAY_FAILED) is set in case of unrecoverable error while trying to send. It will also be used to know when it's safe to close the connection. --- diff --git a/src/mux_h2.c b/src/mux_h2.c index 08b6657098..57070bf705 100644 --- a/src/mux_h2.c +++ b/src/mux_h2.c @@ -48,6 +48,11 @@ static struct pool_head *pool2_h2s; #define H2_CF_DEM_SFULL 0x00000080 // demux blocked on stream request buffer full #define H2_CF_DEM_BLOCK_ANY 0x000000FC // aggregate of the demux flags above +/* other flags */ +#define H2_CF_GOAWAY_SENT 0x00000100 // a GOAWAY frame was successfully sent +#define H2_CF_GOAWAY_FAILED 0x00000200 // a GOAWAY frame failed to be sent + + /* H2 connection state, in h2c->st0 */ enum h2_cs { H2_CS_PREFACE, // init done, waiting for connection preface @@ -576,9 +581,78 @@ static struct h2s *h2c_stream_new(struct h2c *h2c, int id) return h2s; } +/* try to send a GOAWAY frame on the connection to report an error or a graceful + * shutdown, with h2c->errcode as the error code. Returns > 0 on success or zero + * if nothing was done. It uses h2c->last_sid as the advertised ID, or copies it + * from h2c->max_id if it's not set yet (<0). In case of lack of room to write + * the message, it subscribes the requester (either or ) to future + * notifications. It sets H2_CF_GOAWAY_SENT on success, and H2_CF_GOAWAY_FAILED + * on unrecoverable failure. It will not attempt to send one again in this last + * case so that it is safe to use h2c_error() to report such errors. + */ +static int h2c_send_goaway_error(struct h2c *h2c, struct h2s *h2s) +{ + struct buffer *res; + char str[17]; + int ret; + + if (h2c->flags & H2_CF_GOAWAY_FAILED) + return 1; // claim that it worked + + if (h2c_mux_busy(h2c, h2s)) { + if (h2s) + h2s->flags |= H2_SF_BLK_MBUSY; + else + h2c->flags |= H2_CF_DEM_MBUSY; + return 0; + } + + res = h2_get_mbuf(h2c); + if (!res) { + h2c->flags |= H2_CF_MUX_MALLOC; + if (h2s) + h2s->flags |= H2_SF_BLK_MROOM; + else + h2c->flags |= H2_CF_DEM_MROOM; + return 0; + } + + /* len: 8, type: 7, flags: none, sid: 0 */ + memcpy(str, "\x00\x00\x08\x07\x00\x00\x00\x00\x00", 9); + + if (h2c->last_sid < 0) + h2c->last_sid = h2c->max_id; + + write_n32(str + 9, h2c->last_sid); + write_n32(str + 13, h2c->errcode); + ret = bo_istput(res, ist2(str, 17)); + if (unlikely(ret <= 0)) { + if (!ret) { + h2c->flags |= H2_CF_MUX_MFULL; + if (h2s) + h2s->flags |= H2_SF_BLK_MROOM; + else + h2c->flags |= H2_CF_DEM_MROOM; + return 0; + } + else { + /* we cannot report this error using GOAWAY, so we mark + * it and claim a success. + */ + h2c_error(h2c, H2_ERR_INTERNAL_ERROR); + h2c->flags |= H2_CF_GOAWAY_FAILED; + return 1; + } + } + h2c->flags |= H2_CF_GOAWAY_SENT; + return ret; +} + /* process Rx frames to be demultiplexed */ static void h2_process_demux(struct h2c *h2c) { + if (h2c->st0 >= H2_CS_ERROR) + return; } /* process Tx frames from streams to be multiplexed. Returns > 0 if it reached @@ -586,6 +660,18 @@ static void h2_process_demux(struct h2c *h2c) */ static int h2_process_mux(struct h2c *h2c) { + if (unlikely(h2c->st0 > H2_CS_ERROR)) { + if (h2c->st0 == H2_CS_ERROR) { + if (h2c->max_id >= 0) { + h2c_send_goaway_error(h2c, NULL); + if (h2c->flags & H2_CF_MUX_BLOCK_ANY) + return 0; + } + + h2c->st0 = H2_CS_ERROR2; // sent (or failed hard) ! + } + return 1; + } return 1; } @@ -635,6 +721,9 @@ static void h2_recv(struct connection *conn) h2_process_demux(h2c); /* after streams have been processed, we should have made some room */ + if (h2c->st0 >= H2_CS_ERROR) + buf->i = 0; + if (buf->i != buf->size) h2c->flags &= ~H2_CF_DEM_DFULL; return;