"Fossies" - the Fresh Open Source Software Archive  

Source code changes of the file "src/mux_h2.c" between
haproxy-1.9.3.tar.gz and haproxy-1.9.4.tar.gz

About: HAProxy is a TCP/HTTP reverse proxy which is particularly suited for high availability environments.

mux_h2.c  (haproxy-1.9.3):mux_h2.c  (haproxy-1.9.4)
skipping to change at line 89 skipping to change at line 89
/* H2 connection descriptor */ /* H2 connection descriptor */
struct h2c { struct h2c {
struct connection *conn; struct connection *conn;
enum h2_cs st0; /* mux state */ enum h2_cs st0; /* mux state */
enum h2_err errcode; /* H2 err code (H2_ERR_*) */ enum h2_err errcode; /* H2 err code (H2_ERR_*) */
/* 16 bit hole here */ /* 16 bit hole here */
uint32_t flags; /* connection flags: H2_CF_* */ uint32_t flags; /* connection flags: H2_CF_* */
uint32_t streams_limit; /* maximum number of concurrent streams the peer supports */
int32_t max_id; /* highest ID known on this connection, <0 before preface */ int32_t max_id; /* highest ID known on this connection, <0 before preface */
uint32_t rcvd_c; /* newly received data to ACK for the connection */ uint32_t rcvd_c; /* newly received data to ACK for the connection */
uint32_t rcvd_s; /* newly received data to ACK for the current stream (ds i) */ uint32_t rcvd_s; /* newly received data to ACK for the current stream (ds i) */
/* states for the demux direction */ /* states for the demux direction */
struct hpack_dht *ddht; /* demux dynamic header table */ struct hpack_dht *ddht; /* demux dynamic header table */
struct buffer dbuf; /* demux buffer */ struct buffer dbuf; /* demux buffer */
int32_t dsi; /* demux stream ID (<0 = idle) */ int32_t dsi; /* demux stream ID (<0 = idle) */
int32_t dfl; /* demux frame length (if dsi >= 0) */ int32_t dfl; /* demux frame length (if dsi >= 0) */
skipping to change at line 194 skipping to change at line 195
struct session *sess; struct session *sess;
struct h2c *h2c; struct h2c *h2c;
struct h1m h1m; /* request or response parser state for H1 */ struct h1m h1m; /* request or response parser state for H1 */
struct eb32_node by_id; /* place in h2c's streams_by_id */ struct eb32_node by_id; /* place in h2c's streams_by_id */
int32_t id; /* stream ID */ int32_t id; /* stream ID */
uint32_t flags; /* H2_SF_* */ uint32_t flags; /* H2_SF_* */
int mws; /* mux window size for this stream */ int mws; /* mux window size for this stream */
enum h2_err errcode; /* H2 err code (H2_ERR_*) */ enum h2_err errcode; /* H2 err code (H2_ERR_*) */
enum h2_ss st; enum h2_ss st;
uint16_t status; /* HTTP response status */ uint16_t status; /* HTTP response status */
unsigned long long body_len; /* remaining body length according to conten t-length if H2_SF_DATA_CLEN */
struct buffer rxbuf; /* receive buffer, always valid (buf_empty or real b uffer) */ struct buffer rxbuf; /* receive buffer, always valid (buf_empty or real b uffer) */
struct wait_event wait_event; /* Wait list, when we're attempting to send a RST but we can't send */ struct wait_event wait_event; /* Wait list, when we're attempting to send a RST but we can't send */
struct wait_event *recv_wait; /* Address of the wait_event the conn_strea m associated is waiting on */ struct wait_event *recv_wait; /* Address of the wait_event the conn_strea m associated is waiting on */
struct wait_event *send_wait; /* The streeam is waiting for flow control */ struct wait_event *send_wait; /* The streeam is waiting for flow control */
struct list list; /* To be used when adding in h2c->send_list or h2c->fct l_lsit */ struct list list; /* To be used when adding in h2c->send_list or h2c->fct l_lsit */
}; };
/* descriptor for an h2 frame header */ /* descriptor for an h2 frame header */
struct h2_fh { struct h2_fh {
uint32_t len; /* length, host order, 24 bits */ uint32_t len; /* length, host order, 24 bits */
skipping to change at line 225 skipping to change at line 227
/* The default connection window size is 65535, it may only be enlarged using /* The default connection window size is 65535, it may only be enlarged using
* a WINDOW_UPDATE message. Since the window must never be larger than 2G-1, * a WINDOW_UPDATE message. Since the window must never be larger than 2G-1,
* we'll pretend we already received the difference between the two to send * we'll pretend we already received the difference between the two to send
* an equivalent window update to enlarge it to 2G-1. * an equivalent window update to enlarge it to 2G-1.
*/ */
#define H2_INITIAL_WINDOW_INCREMENT ((1U<<31)-1 - 65535) #define H2_INITIAL_WINDOW_INCREMENT ((1U<<31)-1 - 65535)
/* a few settings from the global section */ /* a few settings from the global section */
static int h2_settings_header_table_size = 4096; /* initial value */ static int h2_settings_header_table_size = 4096; /* initial value */
static int h2_settings_initial_window_size = 65535; /* initial value */ static int h2_settings_initial_window_size = 65535; /* initial value */
static int h2_settings_max_concurrent_streams = 100; static unsigned int h2_settings_max_concurrent_streams = 100;
/* a dmumy closed stream */ /* a dmumy closed stream */
static const struct h2s *h2_closed_stream = &(const struct h2s){ static const struct h2s *h2_closed_stream = &(const struct h2s){
.cs = NULL, .cs = NULL,
.h2c = NULL, .h2c = NULL,
.st = H2_SS_CLOSED, .st = H2_SS_CLOSED,
.errcode = H2_ERR_STREAM_CLOSED, .errcode = H2_ERR_STREAM_CLOSED,
.flags = H2_SF_RST_RCVD, .flags = H2_SF_RST_RCVD,
.id = 0, .id = 0,
}; };
skipping to change at line 272 skipping to change at line 274
.errcode = H2_ERR_STREAM_CLOSED, .errcode = H2_ERR_STREAM_CLOSED,
.id = 0, .id = 0,
}; };
static struct task *h2_timeout_task(struct task *t, void *context, unsigned shor t state); static struct task *h2_timeout_task(struct task *t, void *context, unsigned shor t state);
static int h2_send(struct h2c *h2c); static int h2_send(struct h2c *h2c);
static int h2_recv(struct h2c *h2c); static int h2_recv(struct h2c *h2c);
static int h2_process(struct h2c *h2c); static int h2_process(struct h2c *h2c);
static struct task *h2_io_cb(struct task *t, void *ctx, unsigned short state); static struct task *h2_io_cb(struct task *t, void *ctx, unsigned short state);
static inline struct h2s *h2c_st_by_id(struct h2c *h2c, int id); static inline struct h2s *h2c_st_by_id(struct h2c *h2c, int id);
static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *f lags); static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *f lags, unsigned long long *body_len);
static int h2_frt_transfer_data(struct h2s *h2s); static int h2_frt_transfer_data(struct h2s *h2s);
static struct task *h2_deferred_shut(struct task *t, void *ctx, unsigned short s tate); static struct task *h2_deferred_shut(struct task *t, void *ctx, unsigned short s tate);
static struct h2s *h2c_bck_stream_new(struct h2c *h2c, struct conn_stream *cs, s truct session *sess); static struct h2s *h2c_bck_stream_new(struct h2c *h2c, struct conn_stream *cs, s truct session *sess);
static void h2s_alert(struct h2s *h2s); static void h2s_alert(struct h2s *h2s);
/*****************************************************/ /*****************************************************/
/* functions below are for dynamic buffer management */ /* functions below are for dynamic buffer management */
/*****************************************************/ /*****************************************************/
/* indicates whether or not the we may call the h2_recv() function to attempt /* indicates whether or not the we may call the h2_recv() function to attempt
skipping to change at line 323 skipping to change at line 325
/* restarts reading on the connection if it was not enabled */ /* restarts reading on the connection if it was not enabled */
static inline void h2c_restart_reading(const struct h2c *h2c) static inline void h2c_restart_reading(const struct h2c *h2c)
{ {
if (!h2_recv_allowed(h2c)) if (!h2_recv_allowed(h2c))
return; return;
if (!b_data(&h2c->dbuf) && (h2c->wait_event.events & SUB_RETRY_RECV)) if (!b_data(&h2c->dbuf) && (h2c->wait_event.events & SUB_RETRY_RECV))
return; return;
tasklet_wakeup(h2c->wait_event.task); tasklet_wakeup(h2c->wait_event.task);
} }
/* returns true if the connection has too many conn_streams attached */ /* returns true if the front connection has too many conn_streams attached */
static inline int h2_has_too_many_cs(const struct h2c *h2c) static inline int h2_frt_has_too_many_cs(const struct h2c *h2c)
{ {
return h2c->nb_cs > h2_settings_max_concurrent_streams; return h2c->nb_cs > h2_settings_max_concurrent_streams;
} }
/* Tries to grab a buffer and to re-enable processing on mux <target>. The h2c /* Tries to grab a buffer and to re-enable processing on mux <target>. The h2c
* flags are used to figure what buffer was requested. It returns 1 if the * flags are used to figure what buffer was requested. It returns 1 if the
* allocation succeeds, in which case the connection is woken up, or 0 if it's * allocation succeeds, in which case the connection is woken up, or 0 if it's
* impossible to wake up and we prefer to be woken up later. * impossible to wake up and we prefer to be woken up later.
*/ */
static int h2_buf_available(void *target) static int h2_buf_available(void *target)
skipping to change at line 408 skipping to change at line 410
* reaching the last GOAWAY frame seen. max_id is the last assigned id, * reaching the last GOAWAY frame seen. max_id is the last assigned id,
* nb_reserved is the number of streams which don't yet have an ID. * nb_reserved is the number of streams which don't yet have an ID.
*/ */
ret = (h2c->last_sid >= 0) ? h2c->last_sid : 0x7FFFFFFF; ret = (h2c->last_sid >= 0) ? h2c->last_sid : 0x7FFFFFFF;
ret = (unsigned int)(ret - h2c->max_id) / 2 - h2c->nb_reserved - 1; ret = (unsigned int)(ret - h2c->max_id) / 2 - h2c->nb_reserved - 1;
if (ret < 0) if (ret < 0)
ret = 0; ret = 0;
return ret; return ret;
} }
/* returns the number of streams in use on a connection to figure if it's
* idle or not. We check nb_cs and not nb_streams as the caller will want
* to know if it was the last one after a detach().
*/
static int h2_used_streams(struct connection *conn)
{
struct h2c *h2c = conn->ctx;
return h2c->nb_cs;
}
/* returns the number of concurrent streams available on the connection */ /* returns the number of concurrent streams available on the connection */
static int h2_avail_streams(struct connection *conn) static int h2_avail_streams(struct connection *conn)
{ {
struct server *srv = objt_server(conn->target); struct server *srv = objt_server(conn->target);
struct h2c *h2c = conn->ctx; struct h2c *h2c = conn->ctx;
int ret1, ret2; int ret1, ret2;
/* RFC7540#6.8: Receivers of a GOAWAY frame MUST NOT open additional /* RFC7540#6.8: Receivers of a GOAWAY frame MUST NOT open additional
* streams on the connection. * streams on the connection.
*/ */
if (h2c->last_sid >= 0) if (h2c->last_sid >= 0)
return 0; return 0;
/* XXX Should use the negociated max concurrent stream nb instead of the /* note: may be negative if a SETTINGS frame changes the limit */
conf value */ ret1 = h2c->streams_limit - h2c->nb_streams;
ret1 = h2_settings_max_concurrent_streams - h2c->nb_streams;
/* we must also consider the limit imposed by stream IDs */ /* we must also consider the limit imposed by stream IDs */
ret2 = h2_streams_left(h2c); ret2 = h2_streams_left(h2c);
ret1 = MIN(ret1, ret2); ret1 = MIN(ret1, ret2);
if (ret1 && srv && srv->max_reuse >= 0) { if (ret1 > 0 &amp;& srv && srv->max_reuse >= 0) {
ret2 = h2c->stream_cnt <= srv->max_reuse ? srv->max_reuse - h2c-> stream_cnt + 1: 0; ret2 = h2c->stream_cnt <= srv->max_reuse ? srv->max_reuse - h2c-> stream_cnt + 1: 0;
ret1 = MIN(ret1, ret2); ret1 = MIN(ret1, ret2);
} }
return ret1; return ret1;
} }
static int h2_max_streams(struct connection *conn)
{
/* XXX Should use the negociated max concurrent stream nb instead of the
conf value */
return h2_settings_max_concurrent_streams;
}
/*****************************************************************/ /*****************************************************************/
/* functions below are dedicated to the mux setup and management */ /* functions below are dedicated to the mux setup and management */
/*****************************************************************/ /*****************************************************************/
/* Initialize the mux once it's attached. For outgoing connections, the context /* Initialize the mux once it's attached. For outgoing connections, the context
* is already initialized before installing the mux, so we detect incoming * is already initialized before installing the mux, so we detect incoming
* connections from the fact that the context is still NULL. Returns < 0 on * connections from the fact that the context is still NULL. Returns < 0 on
* error. * error.
*/ */
static int h2_init(struct connection *conn, struct proxy *prx, struct session *s ess) static int h2_init(struct connection *conn, struct proxy *prx, struct session *s ess)
skipping to change at line 497 skipping to change at line 504
h2c->wait_event.task->context = h2c; h2c->wait_event.task->context = h2c;
h2c->wait_event.events = 0; h2c->wait_event.events = 0;
h2c->ddht = hpack_dht_alloc(h2_settings_header_table_size); h2c->ddht = hpack_dht_alloc(h2_settings_header_table_size);
if (!h2c->ddht) if (!h2c->ddht)
goto fail; goto fail;
/* Initialise the context. */ /* Initialise the context. */
h2c->st0 = H2_CS_PREFACE; h2c->st0 = H2_CS_PREFACE;
h2c->conn = conn; h2c->conn = conn;
h2c->streams_limit = h2_settings_max_concurrent_streams;
h2c->max_id = -1; h2c->max_id = -1;
h2c->errcode = H2_ERR_NO_ERROR; h2c->errcode = H2_ERR_NO_ERROR;
h2c->rcvd_c = 0; h2c->rcvd_c = 0;
h2c->rcvd_s = 0; h2c->rcvd_s = 0;
h2c->nb_streams = 0; h2c->nb_streams = 0;
h2c->nb_cs = 0; h2c->nb_cs = 0;
h2c->nb_reserved = 0; h2c->nb_reserved = 0;
h2c->stream_cnt = 0; h2c->stream_cnt = 0;
h2c->dbuf = BUF_NULL; h2c->dbuf = BUF_NULL;
skipping to change at line 875 skipping to change at line 883
h2s->wait_event.handle = NULL; h2s->wait_event.handle = NULL;
h2s->wait_event.events = 0; h2s->wait_event.events = 0;
LIST_INIT(&h2s->list); LIST_INIT(&h2s->list);
h2s->h2c = h2c; h2s->h2c = h2c;
h2s->cs = NULL; h2s->cs = NULL;
h2s->mws = h2c->miw; h2s->mws = h2c->miw;
h2s->flags = H2_SF_NONE; h2s->flags = H2_SF_NONE;
h2s->errcode = H2_ERR_NO_ERROR; h2s->errcode = H2_ERR_NO_ERROR;
h2s->st = H2_SS_IDLE; h2s->st = H2_SS_IDLE;
h2s->status = 0; h2s->status = 0;
h2s->body_len = 0;
h2s->rxbuf = BUF_NULL; h2s->rxbuf = BUF_NULL;
if (h2c->flags & H2_CF_IS_BACK) { if (h2c->flags & H2_CF_IS_BACK) {
h1m_init_req(&h2s->h1m); h1m_init_req(&h2s->h1m);
h2s->h1m.err_pos = -1; // don't care about errors on the request path h2s->h1m.err_pos = -1; // don't care about errors on the request path
h2s->h1m.flags |= H1_MF_TOLOWER; h2s->h1m.flags |= H1_MF_TOLOWER;
} else { } else {
h1m_init_res(&h2s->h1m); h1m_init_res(&h2s->h1m);
h2s->h1m.err_pos = -1; // don't care about errors on the response path h2s->h1m.err_pos = -1; // don't care about errors on the response path
h2s->h1m.flags |= H1_MF_TOLOWER; h2s->h1m.flags |= H1_MF_TOLOWER;
skipping to change at line 943 skipping to change at line 952
/* We want the accept date presented to the next stream to be the one /* We want the accept date presented to the next stream to be the one
* we have now, the handshake time to be null (since the next stream * we have now, the handshake time to be null (since the next stream
* is not delayed by a handshake), and the idle time to count since * is not delayed by a handshake), and the idle time to count since
* right now. * right now.
*/ */
sess->accept_date = date; sess->accept_date = date;
sess->tv_accept = now; sess->tv_accept = now;
sess->t_handshake = 0; sess->t_handshake = 0;
/* OK done, the stream lives its own life now */ /* OK done, the stream lives its own life now */
if (h2_has_too_many_cs(h2c)) if (h2_frt_has_too_many_cs(h2c))
h2c->flags |= H2_CF_DEM_TOOMANY; h2c->flags |= H2_CF_DEM_TOOMANY;
return h2s; return h2s;
out_free_cs: out_free_cs:
h2c->nb_cs--; h2c->nb_cs--;
cs_free(cs); cs_free(cs);
out_close: out_close:
h2s_destroy(h2s); h2s_destroy(h2s);
out: out:
sess_log(sess); sess_log(sess);
skipping to change at line 965 skipping to change at line 974
} }
/* allocates a new stream associated to conn_stream <cs> on the h2c connection /* allocates a new stream associated to conn_stream <cs> on the h2c connection
* and returns it, or NULL in case of memory allocation error or if the highest * and returns it, or NULL in case of memory allocation error or if the highest
* possible stream ID was reached. * possible stream ID was reached.
*/ */
static struct h2s *h2c_bck_stream_new(struct h2c *h2c, struct conn_stream *cs, s truct session *sess) static struct h2s *h2c_bck_stream_new(struct h2c *h2c, struct conn_stream *cs, s truct session *sess)
{ {
struct h2s *h2s = NULL; struct h2s *h2s = NULL;
if (h2c->nb_streams >= h2_settings_max_concurrent_streams) if (h2c->nb_streams >= h2c->streams_limit)
goto out; goto out;
if (h2_streams_left(h2c) < 1) if (h2_streams_left(h2c) < 1)
goto out; goto out;
/* Defer choosing the ID until we send the first message to create the st ream */ /* Defer choosing the ID until we send the first message to create the st ream */
h2s = h2s_new(h2c, 0); h2s = h2s_new(h2c, 0);
if (!h2s) if (!h2s)
goto out; goto out;
skipping to change at line 1422 skipping to change at line 1431
struct h2s *h2s; struct h2s *h2s;
struct eb32_node *node; struct eb32_node *node;
if (!diff) if (!diff)
return; return;
node = eb32_first(&h2c->streams_by_id); node = eb32_first(&h2c->streams_by_id);
while (node) { while (node) {
h2s = container_of(node, struct h2s, by_id); h2s = container_of(node, struct h2s, by_id);
h2s->mws += diff; h2s->mws += diff;
if (h2s->mws > 0 && (h2s->flags & H2_SF_BLK_SFCTL)) {
h2s->flags &= ~H2_SF_BLK_SFCTL;
if (h2s->send_wait)
LIST_ADDQ(&h2c->send_list, &h2s->list);
}
node = eb32_next(node); node = eb32_next(node);
} }
} }
/* processes a SETTINGS frame whose payload is <payload> for <plen> bytes, and /* processes a SETTINGS frame whose payload is <payload> for <plen> bytes, and
* ACKs it if needed. Returns > 0 on success or zero on missing data. It may * ACKs it if needed. Returns > 0 on success or zero on missing data. It may
* return an error in h2c. Described in RFC7540#6.5. * return an error in h2c. The caller must have already verified frame length
* and stream ID validity. Described in RFC7540#6.5.
*/ */
static int h2c_handle_settings(struct h2c *h2c) static int h2c_handle_settings(struct h2c *h2c)
{ {
unsigned int offset; unsigned int offset;
int error; int error;
if (h2c->dff & H2_F_SETTINGS_ACK) { if (h2c->dff & H2_F_SETTINGS_ACK) {
if (h2c->dfl) { if (h2c->dfl) {
error = H2_ERR_FRAME_SIZE_ERROR; error = H2_ERR_FRAME_SIZE_ERROR;
goto fail; goto fail;
} }
return 1; return 1;
} }
if (h2c->dsi != 0) {
error = H2_ERR_PROTOCOL_ERROR;
goto fail;
}
if (h2c->dfl % 6) {
error = H2_ERR_FRAME_SIZE_ERROR;
goto fail;
}
/* that's the limit we can process */
if (h2c->dfl > global.tune.bufsize) {
error = H2_ERR_FRAME_SIZE_ERROR;
goto fail;
}
/* process full frame only */ /* process full frame only */
if (b_data(&h2c->dbuf) < h2c->dfl) if (b_data(&h2c->dbuf) < h2c->dfl)
return 0; return 0;
/* parse the frame */ /* parse the frame */
for (offset = 0; offset < h2c->dfl; offset += 6) { for (offset = 0; offset < h2c->dfl; offset += 6) {
uint16_t type = h2_get_n16(&h2c->dbuf, offset); uint16_t type = h2_get_n16(&h2c->dbuf, offset);
int32_t arg = h2_get_n32(&h2c->dbuf, offset + 2); int32_t arg = h2_get_n32(&h2c->dbuf, offset + 2);
switch (type) { switch (type) {
skipping to change at line 1493 skipping to change at line 1495
goto fail; goto fail;
} }
h2c->mfs = arg; h2c->mfs = arg;
break; break;
case H2_SETTINGS_ENABLE_PUSH: case H2_SETTINGS_ENABLE_PUSH:
if (arg < 0 || arg > 1) { // RFC7540#6.5.2 if (arg < 0 || arg > 1) { // RFC7540#6.5.2
error = H2_ERR_PROTOCOL_ERROR; error = H2_ERR_PROTOCOL_ERROR;
goto fail; goto fail;
} }
break; break;
case H2_SETTINGS_MAX_CONCURRENT_STREAMS:
if (h2c->flags & H2_CF_IS_BACK) {
/* the limit is only for the backend; for the fro
ntend it is our limit */
if ((unsigned int)arg > h2_settings_max_concurren
t_streams)
arg = h2_settings_max_concurrent_streams;
h2c->streams_limit = arg;
}
break;
} }
} }
/* need to ACK this frame now */ /* need to ACK this frame now */
h2c->st0 = H2_CS_FRAME_A; h2c->st0 = H2_CS_FRAME_A;
return 1; return 1;
fail: fail:
sess_log(h2c->conn->owner); sess_log(h2c->conn->owner);
h2c_error(h2c, error); h2c_error(h2c, error);
return 0; return 0;
skipping to change at line 1548 skipping to change at line 1558
else { else {
h2c_error(h2c, H2_ERR_INTERNAL_ERROR); h2c_error(h2c, H2_ERR_INTERNAL_ERROR);
return 0; return 0;
} }
} }
return ret; return ret;
} }
/* processes a PING frame and schedules an ACK if needed. The caller must pass /* processes a PING frame and schedules an ACK if needed. The caller must pass
* the pointer to the payload in <payload>. Returns > 0 on success or zero on * the pointer to the payload in <payload>. Returns > 0 on success or zero on
* missing data. It may return an error in h2c. * missing data. The caller must have already verified frame length
* and stream ID validity.
*/ */
static int h2c_handle_ping(struct h2c *h2c) static int h2c_handle_ping(struct h2c *h2c)
{ {
/* frame length must be exactly 8 */
if (h2c->dfl != 8) {
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
return 0;
}
/* schedule a response */ /* schedule a response */
if (!(h2c->dff & H2_F_PING_ACK)) if (!(h2c->dff & H2_F_PING_ACK))
h2c->st0 = H2_CS_FRAME_A; h2c->st0 = H2_CS_FRAME_A;
return 1; return 1;
} }
/* Try to send a window update for stream id <sid> and value <increment>. /* Try to send a window update for stream id <sid> and value <increment>.
* Returns > 0 on success or zero on missing room or failure. It may return an * Returns > 0 on success or zero on missing room or failure. It may return an
* error in h2c. * error in h2c.
*/ */
skipping to change at line 1702 skipping to change at line 1707
else { else {
h2c_error(h2c, H2_ERR_INTERNAL_ERROR); h2c_error(h2c, H2_ERR_INTERNAL_ERROR);
return 0; return 0;
} }
} }
return ret; return ret;
} }
/* processes a WINDOW_UPDATE frame whose payload is <payload> for <plen> bytes. /* processes a WINDOW_UPDATE frame whose payload is <payload> for <plen> bytes.
* Returns > 0 on success or zero on missing data. It may return an error in * Returns > 0 on success or zero on missing data. It may return an error in
* h2c or h2s. Described in RFC7540#6.9. * h2c or h2s. The caller must have already verified frame length and stream ID
* validity. Described in RFC7540#6.9.
*/ */
static int h2c_handle_window_update(struct h2c *h2c, struct h2s *h2s) static int h2c_handle_window_update(struct h2c *h2c, struct h2s *h2s)
{ {
int32_t inc; int32_t inc;
int error; int error;
if (h2c->dfl != 4) {
error = H2_ERR_FRAME_SIZE_ERROR;
goto conn_err;
}
/* process full frame only */ /* process full frame only */
if (b_data(&h2c->dbuf) < h2c->dfl) if (b_data(&h2c->dbuf) < h2c->dfl)
return 0; return 0;
inc = h2_get_n32(&h2c->dbuf, 0); inc = h2_get_n32(&h2c->dbuf, 0);
if (h2c->dsi != 0) { if (h2c->dsi != 0) {
/* stream window update */ /* stream window update */
/* it's not an error to receive WU on a closed stream */ /* it's not an error to receive WU on a closed stream */
skipping to change at line 1767 skipping to change at line 1768
h2c->mws += inc; h2c->mws += inc;
} }
return 1; return 1;
conn_err: conn_err:
h2c_error(h2c, error); h2c_error(h2c, error);
return 0; return 0;
strm_err: strm_err:
if (h2s) { h2s_error(h2s, error);
h2s_error(h2s, error); h2c->st0 = H2_CS_FRAME_E;
h2c->st0 = H2_CS_FRAME_E;
}
else
h2c_error(h2c, error);
return 0; return 0;
} }
/* processes a GOAWAY frame, and signals all streams whose ID is greater than /* processes a GOAWAY frame, and signals all streams whose ID is greater than
* the last ID. Returns > 0 on success or zero on missing data. It may return * the last ID. Returns > 0 on success or zero on missing data. The caller must
* an error in h2c. Described in RFC7540#6.8. * have already verified frame length and stream ID validity. Described in
* RFC7540#6.8.
*/ */
static int h2c_handle_goaway(struct h2c *h2c) static int h2c_handle_goaway(struct h2c *h2c)
{ {
int error;
int last; int last;
if (h2c->dsi != 0) {
error = H2_ERR_PROTOCOL_ERROR;
goto conn_err;
}
if (h2c->dfl < 8) {
error = H2_ERR_FRAME_SIZE_ERROR;
goto conn_err;
}
/* process full frame only */ /* process full frame only */
if (b_data(&h2c->dbuf) < h2c->dfl) if (b_data(&h2c->dbuf) < h2c->dfl)
return 0; return 0;
last = h2_get_n32(&h2c->dbuf, 0); last = h2_get_n32(&h2c->dbuf, 0);
h2c->errcode = h2_get_n32(&h2c->dbuf, 4); h2c->errcode = h2_get_n32(&h2c->dbuf, 4);
h2_wake_some_streams(h2c, last, CS_FL_ERR_PENDING); h2_wake_some_streams(h2c, last, CS_FL_ERR_PENDING);
if (h2c->last_sid < 0) if (h2c->last_sid < 0)
h2c->last_sid = last; h2c->last_sid = last;
return 1; return 1;
conn_err:
h2c_error(h2c, error);
return 0;
} }
/* processes a PRIORITY frame, and either skips it or rejects if it is /* processes a PRIORITY frame, and either skips it or rejects if it is
* invalid. Returns > 0 on success or zero on missing data. It may return * invalid. Returns > 0 on success or zero on missing data. It may return an
* an error in h2c. Described in RFC7540#6.3. * error in h2c. The caller must have already verified frame length and stream
* ID validity. Described in RFC7540#6.3.
*/ */
static int h2c_handle_priority(struct h2c *h2c) static int h2c_handle_priority(struct h2c *h2c)
{ {
int error;
if (h2c->dsi == 0) {
error = H2_ERR_PROTOCOL_ERROR;
goto conn_err;
}
if (h2c->dfl != 5) {
error = H2_ERR_FRAME_SIZE_ERROR;
goto conn_err;
}
/* process full frame only */ /* process full frame only */
if (b_data(&h2c->dbuf) < h2c->dfl) if (b_data(&h2c->dbuf) < h2c->dfl)
return 0; return 0;
if (h2_get_n32(&h2c->dbuf, 0) == h2c->dsi) { if (h2_get_n32(&h2c->dbuf, 0) == h2c->dsi) {
/* 7540#5.3 : can't depend on itself */ /* 7540#5.3 : can't depend on itself */
error = H2_ERR_PROTOCOL_ERROR; h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
goto conn_err; return 0;
} }
return 1; return 1;
conn_err:
h2c_error(h2c, error);
return 0;
} }
/* processes an RST_STREAM frame, and sets the 32-bit error code on the stream. /* processes an RST_STREAM frame, and sets the 32-bit error code on the stream.
* Returns > 0 on success or zero on missing data. It may return an error in * Returns > 0 on success or zero on missing data. The caller must have already
* h2c. Described in RFC7540#6.4. * verified frame length and stream ID validity. Described in RFC7540#6.4.
*/ */
static int h2c_handle_rst_stream(struct h2c *h2c, struct h2s *h2s) static int h2c_handle_rst_stream(struct h2c *h2c, struct h2s *h2s)
{ {
int error;
if (h2c->dsi == 0) {
error = H2_ERR_PROTOCOL_ERROR;
goto conn_err;
}
if (h2c->dfl != 4) {
error = H2_ERR_FRAME_SIZE_ERROR;
goto conn_err;
}
/* process full frame only */ /* process full frame only */
if (b_data(&h2c->dbuf) < h2c->dfl) if (b_data(&h2c->dbuf) < h2c->dfl)
return 0; return 0;
/* late RST, already handled */ /* late RST, already handled */
if (h2s->st == H2_SS_CLOSED) if (h2s->st == H2_SS_CLOSED)
return 1; return 1;
h2s->errcode = h2_get_n32(&h2c->dbuf, 0); h2s->errcode = h2_get_n32(&h2c->dbuf, 0);
h2s_close(h2s); h2s_close(h2s);
if (h2s->cs) { if (h2s->cs) {
cs_set_error(h2s->cs); cs_set_error(h2s->cs);
h2s_alert(h2s); h2s_alert(h2s);
} }
h2s->flags |= H2_SF_RST_RCVD; h2s->flags |= H2_SF_RST_RCVD;
return 1; return 1;
conn_err:
h2c_error(h2c, error);
return 0;
} }
/* processes a HEADERS frame. Returns h2s on success or NULL on missing data. /* processes a HEADERS frame. Returns h2s on success or NULL on missing data.
* It may return an error in h2c or h2s. The caller must consider that the * It may return an error in h2c or h2s. The caller must consider that the
* return value is the new h2s in case one was allocated (most common case). * return value is the new h2s in case one was allocated (most common case).
* Described in RFC7540#6.2. Most of the * Described in RFC7540#6.2. Most of the
* errors here are reported as connection errors since it's impossible to * errors here are reported as connection errors since it's impossible to
* recover from such errors after the compression context has been altered. * recover from such errors after the compression context has been altered.
*/ */
static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s) static struct h2s *h2c_frt_handle_headers(struct h2c *h2c, struct h2s *h2s)
{ {
struct buffer rxbuf = BUF_NULL; struct buffer rxbuf = BUF_NULL;
unsigned long long body_len = 0;
uint32_t flags = 0; uint32_t flags = 0;
int error; int error;
if (!h2c->dfl) {
/* RFC7540#4.2 */
error = H2_ERR_FRAME_SIZE_ERROR; // empty headers frame!
sess_log(h2c->conn->owner);
goto conn_err;
}
if (!b_size(&h2c->dbuf)) if (!b_size(&h2c->dbuf))
return NULL; // empty buffer return NULL; // empty buffer
if (b_data(&h2c->dbuf) < h2c->dfl && !b_full(&h2c->dbuf)) if (b_data(&h2c->dbuf) < h2c->dfl && !b_full(&h2c->dbuf))
return NULL; // incomplete frame return NULL; // incomplete frame
/* now either the frame is complete or the buffer is complete */ /* now either the frame is complete or the buffer is complete */
if (h2s->st != H2_SS_IDLE) { if (h2s->st != H2_SS_IDLE) {
/* The stream exists/existed, this must be a trailers frame */ /* The stream exists/existed, this must be a trailers frame */
if (h2s->st != H2_SS_CLOSED) { if (h2s->st != H2_SS_CLOSED) {
if (h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags) <= 0) if (h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags, &bo dy_len) <= 0)
goto out; goto out;
goto done; goto done;
} }
error = H2_ERR_PROTOCOL_ERROR; /* the connection was already killed by an RST, let's consume
sess_log(h2c->conn->owner); * the data and send another RST.
goto conn_err; */
error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len);
h2s = (struct h2s*)h2_error_stream;
goto send_rst;
} }
else if (h2c->dsi <= h2c->max_id || !(h2c->dsi & 1)) { else if (h2c->dsi <= h2c->max_id || !(h2c->dsi & 1)) {
/* RFC7540#5.1.1 stream id > prev ones, and must be odd here */ /* RFC7540#5.1.1 stream id > prev ones, and must be odd here */
error = H2_ERR_PROTOCOL_ERROR; error = H2_ERR_PROTOCOL_ERROR;
sess_log(h2c->conn->owner); sess_log(h2c->conn->owner);
goto conn_err; goto conn_err;
} }
else if (h2c->flags & H2_CF_DEM_TOOMANY) else if (h2c->flags & H2_CF_DEM_TOOMANY)
goto out; // IDLE but too many cs still present goto out; // IDLE but too many cs still present
error = h2c_decode_headers(h2c, &rxbuf, &flags); error = h2c_decode_headers(h2c, &rxbuf, &flags, &body_len);
/* unrecoverable error ? */ /* unrecoverable error ? */
if (h2c->st0 >= H2_CS_ERROR) if (h2c->st0 >= H2_CS_ERROR)
goto out; goto out;
if (error <= 0) { if (error <= 0) {
if (error == 0) if (error == 0)
goto out; // missing data goto out; // missing data
/* Failed to decode this stream (e.g. too large request) /* Failed to decode this stream (e.g. too large request)
skipping to change at line 1964 skipping to change at line 1913
*/ */
h2s = h2c_frt_stream_new(h2c, h2c->dsi); h2s = h2c_frt_stream_new(h2c, h2c->dsi);
if (!h2s) { if (!h2s) {
h2s = (struct h2s*)h2_refused_stream; h2s = (struct h2s*)h2_refused_stream;
goto send_rst; goto send_rst;
} }
h2s->st = H2_SS_OPEN; h2s->st = H2_SS_OPEN;
h2s->rxbuf = rxbuf; h2s->rxbuf = rxbuf;
h2s->flags |= flags; h2s->flags |= flags;
h2s->body_len = body_len;
done: done:
if (h2c->dff & H2_F_HEADERS_END_STREAM) if (h2c->dff & H2_F_HEADERS_END_STREAM)
h2s->flags |= H2_SF_ES_RCVD; h2s->flags |= H2_SF_ES_RCVD;
if (h2s->flags & H2_SF_ES_RCVD) { if (h2s->flags & H2_SF_ES_RCVD) {
h2s->st = H2_SS_HREM; if (h2s->st == H2_SS_OPEN)
h2s->st = H2_SS_HREM;
else
h2s_close(h2s);
h2s->cs->flags |= CS_FL_REOS; h2s->cs->flags |= CS_FL_REOS;
} }
if (h2s->st >= H2_SS_ERROR) { if (h2s->st >= H2_SS_ERROR) {
/* stream error : send RST_STREAM */ /* stream error : send RST_STREAM */
h2c->st0 = H2_CS_FRAME_E; h2c->st0 = H2_CS_FRAME_E;
} }
else { else {
/* update the max stream ID if the request is being processed */ /* update the max stream ID if the request is being processed */
if (h2s->id > h2c->max_id) if (h2s->id > h2c->max_id)
h2c->max_id = h2s->id; h2c->max_id = h2s->id;
} }
return h2s; return h2s;
conn_err: conn_err:
h2c_error(h2c, error); h2c_error(h2c, error);
goto out; goto out;
strm_err:
if (h2s) {
h2s_error(h2s, error);
h2c->st0 = H2_CS_FRAME_E;
}
else
h2c_error(h2c, error);
out: out:
h2_release_buf(h2c, &rxbuf); h2_release_buf(h2c, &rxbuf);
return NULL; return NULL;
send_rst: send_rst:
/* make the demux send an RST for the current stream. We may only /* make the demux send an RST for the current stream. We may only
* do this if we're certain that the HEADERS frame was properly * do this if we're certain that the HEADERS frame was properly
* decompressed so that the HPACK decoder is still kept up to date. * decompressed so that the HPACK decoder is still kept up to date.
*/ */
h2_release_buf(h2c, &rxbuf); h2_release_buf(h2c, &rxbuf);
skipping to change at line 2020 skipping to change at line 1966
/* processes a HEADERS frame. Returns h2s on success or NULL on missing data. /* processes a HEADERS frame. Returns h2s on success or NULL on missing data.
* It may return an error in h2c or h2s. Described in RFC7540#6.2. Most of the * It may return an error in h2c or h2s. Described in RFC7540#6.2. Most of the
* errors here are reported as connection errors since it's impossible to * errors here are reported as connection errors since it's impossible to
* recover from such errors after the compression context has been altered. * recover from such errors after the compression context has been altered.
*/ */
static struct h2s *h2c_bck_handle_headers(struct h2c *h2c, struct h2s *h2s) static struct h2s *h2c_bck_handle_headers(struct h2c *h2c, struct h2s *h2s)
{ {
int error; int error;
if (!h2c->dfl) {
/* RFC7540#4.2 */
error = H2_ERR_FRAME_SIZE_ERROR; // empty headers frame!
sess_log(h2c->conn->owner);
goto conn_err;
}
if (!b_size(&h2c->dbuf)) if (!b_size(&h2c->dbuf))
return NULL; // empty buffer return NULL; // empty buffer
if (b_data(&h2c->dbuf) < h2c->dfl && !b_full(&h2c->dbuf)) if (b_data(&h2c->dbuf) < h2c->dfl && !b_full(&h2c->dbuf))
return NULL; // incomplete frame return NULL; // incomplete frame
error = h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags); error = h2c_decode_headers(h2c, &h2s->rxbuf, &h2s->flags, &h2s->body_len) ;
/* unrecoverable error ? */ /* unrecoverable error ? */
if (h2c->st0 >= H2_CS_ERROR) if (h2c->st0 >= H2_CS_ERROR)
return NULL; return NULL;
if (h2s->st != H2_SS_OPEN && h2s->st != H2_SS_HLOC) {
/* RFC7540#5.1 */
h2s_error(h2s, H2_ERR_STREAM_CLOSED);
h2c->st0 = H2_CS_FRAME_E;
return NULL;
}
if (error <= 0) { if (error <= 0) {
if (error == 0) if (error == 0)
return NULL; // missing data return NULL; // missing data
/* stream error : send RST_STREAM */ /* stream error : send RST_STREAM */
h2s_error(h2s, H2_ERR_PROTOCOL_ERROR); h2s_error(h2s, H2_ERR_PROTOCOL_ERROR);
h2c->st0 = H2_CS_FRAME_E; h2c->st0 = H2_CS_FRAME_E;
return NULL; return NULL;
} }
skipping to change at line 2062 skipping to change at line 2008
} }
if (h2s->cs->flags & CS_FL_ERROR && h2s->st < H2_SS_ERROR) if (h2s->cs->flags & CS_FL_ERROR && h2s->st < H2_SS_ERROR)
h2s->st = H2_SS_ERROR; h2s->st = H2_SS_ERROR;
else if (h2s->cs->flags & CS_FL_REOS && h2s->st == H2_SS_OPEN) else if (h2s->cs->flags & CS_FL_REOS && h2s->st == H2_SS_OPEN)
h2s->st = H2_SS_HREM; h2s->st = H2_SS_HREM;
else if (h2s->cs->flags & CS_FL_REOS && h2s->st == H2_SS_HLOC) else if (h2s->cs->flags & CS_FL_REOS && h2s->st == H2_SS_HLOC)
h2s_close(h2s); h2s_close(h2s);
return h2s; return h2s;
conn_err:
h2c_error(h2c, error);
return NULL;
strm_err:
if (h2s) {
h2s_error(h2s, error);
h2c->st0 = H2_CS_FRAME_E;
}
else
h2c_error(h2c, error);
return NULL;
} }
/* processes a DATA frame. Returns > 0 on success or zero on missing data. /* processes a DATA frame. Returns > 0 on success or zero on missing data.
* It may return an error in h2c or h2s. Described in RFC7540#6.1. * It may return an error in h2c or h2s. Described in RFC7540#6.1.
*/ */
static int h2c_frt_handle_data(struct h2c *h2c, struct h2s *h2s) static int h2c_frt_handle_data(struct h2c *h2c, struct h2s *h2s)
{ {
int error; int error;
/* note that empty DATA frames are perfectly valid and sometimes used /* note that empty DATA frames are perfectly valid and sometimes used
skipping to change at line 2096 skipping to change at line 2029
*/ */
if (!b_size(&h2c->dbuf) && h2c->dfl) if (!b_size(&h2c->dbuf) && h2c->dfl)
return 0; // empty buffer return 0; // empty buffer
if (b_data(&h2c->dbuf) < h2c->dfl && !b_full(&h2c->dbuf)) if (b_data(&h2c->dbuf) < h2c->dfl && !b_full(&h2c->dbuf))
return 0; // incomplete frame return 0; // incomplete frame
/* now either the frame is complete or the buffer is complete */ /* now either the frame is complete or the buffer is complete */
if (!h2c->dsi) {
/* RFC7540#6.1 */
error = H2_ERR_PROTOCOL_ERROR;
goto conn_err;
}
if (h2s->st != H2_SS_OPEN && h2s->st != H2_SS_HLOC) { if (h2s->st != H2_SS_OPEN && h2s->st != H2_SS_HLOC) {
/* RFC7540#6.1 */ /* RFC7540#6.1 */
error = H2_ERR_STREAM_CLOSED; error = H2_ERR_STREAM_CLOSED;
goto strm_err; goto strm_err;
} }
if ((h2s->flags & H2_SF_DATA_CLEN) && h2c->dfl > h2s->body_len) {
/* RFC7540#8.1.2 */
error = H2_ERR_PROTOCOL_ERROR;
goto strm_err;
}
if (!h2_frt_transfer_data(h2s)) if (!h2_frt_transfer_data(h2s))
return 0; return 0;
/* call the upper layers to process the frame, then let the upper layer /* call the upper layers to process the frame, then let the upper layer
* notify the stream about any change. * notify the stream about any change.
*/ */
if (!h2s->cs) { if (!h2s->cs) {
error = H2_ERR_STREAM_CLOSED; error = H2_ERR_STREAM_CLOSED;
goto strm_err; goto strm_err;
} }
skipping to change at line 2135 skipping to change at line 2068
} }
/* check for completion : the callee will change this to FRAME_A or /* check for completion : the callee will change this to FRAME_A or
* FRAME_H once done. * FRAME_H once done.
*/ */
if (h2c->st0 == H2_CS_FRAME_P) if (h2c->st0 == H2_CS_FRAME_P)
return 0; return 0;
/* last frame */ /* last frame */
if (h2c->dff & H2_F_DATA_END_STREAM) { if (h2c->dff & H2_F_DATA_END_STREAM) {
h2s->st = H2_SS_HREM; if (h2s->st == H2_SS_OPEN)
h2s->st = H2_SS_HREM;
else
h2s_close(h2s);
h2s->flags |= H2_SF_ES_RCVD; h2s->flags |= H2_SF_ES_RCVD;
h2s->cs->flags |= CS_FL_REOS; h2s->cs->flags |= CS_FL_REOS;
if (h2s->flags & H2_SF_DATA_CLEN && h2s->body_len) {
/* RFC7540#8.1.2 */
error = H2_ERR_PROTOCOL_ERROR;
goto strm_err;
}
} }
return 1; return 1;
conn_err:
h2c_error(h2c, error);
return 0;
strm_err: strm_err:
if (h2s) { h2s_error(h2s, error);
h2s_error(h2s, error); h2c->st0 = H2_CS_FRAME_E;
h2c->st0 = H2_CS_FRAME_E;
}
else
h2c_error(h2c, error);
return 0; return 0;
} }
/* process Rx frames to be demultiplexed */ /* process Rx frames to be demultiplexed */
static void h2_process_demux(struct h2c *h2c) static void h2_process_demux(struct h2c *h2c)
{ {
struct h2s *h2s = NULL, *tmp_h2s; struct h2s *h2s = NULL, *tmp_h2s;
struct h2_fh hdr;
unsigned int padlen = 0;
if (h2c->st0 >= H2_CS_ERROR) if (h2c->st0 >= H2_CS_ERROR)
return; return;
if (unlikely(h2c->st0 < H2_CS_FRAME_H)) { if (unlikely(h2c->st0 < H2_CS_FRAME_H)) {
if (h2c->st0 == H2_CS_PREFACE) { if (h2c->st0 == H2_CS_PREFACE) {
if (h2c->flags & H2_CF_IS_BACK) if (h2c->flags & H2_CF_IS_BACK)
return; return;
if (unlikely(h2c_frt_recv_preface(h2c) <= 0)) { if (unlikely(h2c_frt_recv_preface(h2c) <= 0)) {
/* RFC7540#3.5: a GOAWAY frame MAY be omitted */ /* RFC7540#3.5: a GOAWAY frame MAY be omitted */
skipping to change at line 2182 skipping to change at line 2119
sess_log(h2c->conn->owner); sess_log(h2c->conn->owner);
} }
goto fail; goto fail;
} }
h2c->max_id = 0; h2c->max_id = 0;
h2c->st0 = H2_CS_SETTINGS1; h2c->st0 = H2_CS_SETTINGS1;
} }
if (h2c->st0 == H2_CS_SETTINGS1) { if (h2c->st0 == H2_CS_SETTINGS1) {
struct h2_fh hdr;
/* ensure that what is pending is a valid SETTINGS frame /* ensure that what is pending is a valid SETTINGS frame
* without an ACK. * without an ACK.
*/ */
if (!h2_get_frame_hdr(&h2c->dbuf, &hdr)) { if (!h2_get_frame_hdr(&h2c->dbuf, &hdr)) {
/* RFC7540#3.5: a GOAWAY frame MAY be omitted */ /* RFC7540#3.5: a GOAWAY frame MAY be omitted */
if (h2c->st0 == H2_CS_ERROR) { if (h2c->st0 == H2_CS_ERROR) {
h2c->st0 = H2_CS_ERROR2; h2c->st0 = H2_CS_ERROR2;
sess_log(h2c->conn->owner); sess_log(h2c->conn->owner);
} }
goto fail; goto fail;
skipping to change at line 2216 skipping to change at line 2151
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR); h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
h2c->st0 = H2_CS_ERROR2; h2c->st0 = H2_CS_ERROR2;
sess_log(h2c->conn->owner); sess_log(h2c->conn->owner);
goto fail; goto fail;
} }
/* that's OK, switch to FRAME_P to process it. This is /* that's OK, switch to FRAME_P to process it. This is
* a SETTINGS frame whose header has already been * a SETTINGS frame whose header has already been
* deleted above. * deleted above.
*/ */
h2c->dfl = hdr.len; padlen = 0;
h2c->dsi = hdr.sid; goto new_frame;
h2c->dft = hdr.ft;
h2c->dff = hdr.ff;
h2c->dpl = 0;
h2c->st0 = H2_CS_FRAME_P;
} }
} }
/* process as many incoming frames as possible below */ /* process as many incoming frames as possible below */
while (b_data(&h2c->dbuf)) { while (b_data(&h2c->dbuf)) {
int ret = 0; int ret = 0;
if (h2c->st0 >= H2_CS_ERROR) if (h2c->st0 >= H2_CS_ERROR)
break; break;
if (h2c->st0 == H2_CS_FRAME_H) { if (h2c->st0 == H2_CS_FRAME_H) {
struct h2_fh hdr;
unsigned int padlen = 0;
if (!h2_peek_frame_hdr(&h2c->dbuf, 0, &hdr)) if (!h2_peek_frame_hdr(&h2c->dbuf, 0, &hdr))
break; break;
if ((int)hdr.len < 0 || (int)hdr.len > global.tune.bufsiz e) { if ((int)hdr.len < 0 || (int)hdr.len > global.tune.bufsiz e) {
h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR); h2c_error(h2c, H2_ERR_FRAME_SIZE_ERROR);
h2c->st0 = H2_CS_ERROR; h2c->st0 = H2_CS_ERROR;
if (!h2c->nb_streams) { if (!h2c->nb_streams) {
/* only log if no other stream can report the error */ /* only log if no other stream can report the error */
sess_log(h2c->conn->owner); sess_log(h2c->conn->owner);
} }
skipping to change at line 2285 skipping to change at line 2213
goto fail; goto fail;
} }
if (h2_ft_bit(hdr.ft) & H2_FT_FC_MASK) { if (h2_ft_bit(hdr.ft) & H2_FT_FC_MASK) {
h2c->rcvd_c++; h2c->rcvd_c++;
h2c->rcvd_s++; h2c->rcvd_s++;
} }
b_del(&h2c->dbuf, 1); b_del(&h2c->dbuf, 1);
} }
h2_skip_frame_hdr(&h2c->dbuf); h2_skip_frame_hdr(&h2c->dbuf);
new_frame:
h2c->dfl = hdr.len; h2c->dfl = hdr.len;
h2c->dsi = hdr.sid; h2c->dsi = hdr.sid;
h2c->dft = hdr.ft; h2c->dft = hdr.ft;
h2c->dff = hdr.ff; h2c->dff = hdr.ff;
h2c->dpl = padlen; h2c->dpl = padlen;
h2c->st0 = H2_CS_FRAME_P; h2c->st0 = H2_CS_FRAME_P;
/* check for minimum basic frame format validity */
ret = h2_frame_check(h2c->dft, 1, h2c->dsi, h2c->dfl, glo
bal.tune.bufsize);
if (ret != H2_ERR_NO_ERROR) {
h2c_error(h2c, ret);
sess_log(h2c->conn->owner);
goto fail;
}
} }
/* Only H2_CS_FRAME_P and H2_CS_FRAME_A here */ /* Only H2_CS_FRAME_P and H2_CS_FRAME_A here */
tmp_h2s = h2c_st_by_id(h2c, h2c->dsi); tmp_h2s = h2c_st_by_id(h2c, h2c->dsi);
if (tmp_h2s != h2s && h2s && h2s->cs && if (tmp_h2s != h2s && h2s && h2s->cs &&
(b_data(&h2s->rxbuf) || (b_data(&h2s->rxbuf) ||
(h2s->cs->flags & (CS_FL_ERROR|CS_FL_ERR_PENDING|CS_FL_EOS|C S_FL_REOS)))) { (h2s->cs->flags & (CS_FL_ERROR|CS_FL_ERR_PENDING|CS_FL_EOS|C S_FL_REOS)))) {
/* we may have to signal the upper layers */ /* we may have to signal the upper layers */
h2s->cs->flags |= CS_FL_RCV_MORE; h2s->cs->flags |= CS_FL_RCV_MORE;
skipping to change at line 2348 skipping to change at line 2286
* streams closed by receiving RST, sending RST, and seeing ES * streams closed by receiving RST, sending RST, and seeing ES
* in both directions. In addition to this, the creation of a * in both directions. In addition to this, the creation of a
* new stream reusing the identifier of a closed one will be * new stream reusing the identifier of a closed one will be
* detected here. Given that we cannot keep track of all closed * detected here. Given that we cannot keep track of all closed
* streams forever, we consider that unknown closed streams were * streams forever, we consider that unknown closed streams were
* closed on RST received, which allows us to respond with an * closed on RST received, which allows us to respond with an
* RST without breaking the connection (eg: to abort a transfer). * RST without breaking the connection (eg: to abort a transfer).
* Some frames have to be silently ignored as well. * Some frames have to be silently ignored as well.
*/ */
if (h2s->st == H2_SS_CLOSED && h2c->dsi) { if (h2s->st == H2_SS_CLOSED && h2c->dsi) {
if (h2_ft_bit(h2c->dft) & H2_FT_HDR_MASK) { if (!(h2c->flags & H2_CF_IS_BACK) && h2_ft_bit(h2c->dft) & H2_FT_HDR_MASK) {
/* #5.1.1: The identifier of a newly /* #5.1.1: The identifier of a newly
* established stream MUST be numerically * established stream MUST be numerically
* greater than all streams that the initiating * greater than all streams that the initiating
* endpoint has opened or reserved. This * endpoint has opened or reserved. This
* governs streams that are opened using a * governs streams that are opened using a
* HEADERS frame and streams that are reserved * HEADERS frame and streams that are reserved
* using PUSH_PROMISE. An endpoint that * using PUSH_PROMISE. An endpoint that
* receives an unexpected stream identifier * receives an unexpected stream identifier
* MUST respond with a connection error. * MUST respond with a connection error.
*/ */
h2c_error(h2c, H2_ERR_STREAM_CLOSED); h2c_error(h2c, H2_ERR_STREAM_CLOSED);
goto strm_err; goto strm_err;
} }
if (h2s->flags & H2_SF_RST_RCVD) { if (h2s->flags & H2_SF_RST_RCVD && h2_ft_bit(h2c->dft) & H2_FT_HDR_MASK) {
/* RFC7540#5.1:closed: an endpoint that /* RFC7540#5.1:closed: an endpoint that
* receives any frame other than PRIORITY after * receives any frame other than PRIORITY after
* receiving a RST_STREAM MUST treat that as a * receiving a RST_STREAM MUST treat that as a
* stream error of type STREAM_CLOSED. * stream error of type STREAM_CLOSED.
* *
* Note that old streams fall into this category * Note that old streams fall into this category
* and will lead to an RST being sent. * and will lead to an RST being sent.
*
* However, we cannot generalize this to all fram
e types. Those
* carrying compression state must still be proce
ssed before
* being dropped or we'll desynchronize the decod
er. This can
* happen with request trailers received after se
nding an
* RST_STREAM, or with header/trailers responses
received after
* sending RST_STREAM (aborted stream).
*/ */
h2s_error(h2s, H2_ERR_STREAM_CLOSED); h2s_error(h2s, H2_ERR_STREAM_CLOSED);
h2c->st0 = H2_CS_FRAME_E; h2c->st0 = H2_CS_FRAME_E;
goto strm_err; goto strm_err;
} }
/* RFC7540#5.1:closed: if this state is reached as a /* RFC7540#5.1:closed: if this state is reached as a
* result of sending a RST_STREAM frame, the peer that * result of sending a RST_STREAM frame, the peer that
* receives the RST_STREAM might have already sent * receives the RST_STREAM might have already sent
* frames on the stream that cannot be withdrawn. An * frames on the stream that cannot be withdrawn. An
* endpoint MUST ignore frames that it receives on * endpoint MUST ignore frames that it receives on
* closed streams after it has sent a RST_STREAM * closed streams after it has sent a RST_STREAM
* frame. An endpoint MAY choose to limit the period * frame. An endpoint MAY choose to limit the period
* over which it ignores frames and treat frames that * over which it ignores frames and treat frames that
* arrive after this time as being in error. * arrive after this time as being in error.
*/ */
if (!(h2s->flags & H2_SF_RST_SENT)) { if (h2s->id && !(h2s->flags & H2_SF_RST_SENT)) {
/* RFC7540#5.1:closed: any frame other than /* RFC7540#5.1:closed: any frame other than
* PRIO/WU/RST in this state MUST be treated as * PRIO/WU/RST in this state MUST be treated as
* a connection error * a connection error
*/ */
if (h2c->dft != H2_FT_RST_STREAM && if (h2c->dft != H2_FT_RST_STREAM &&
h2c->dft != H2_FT_PRIORITY && h2c->dft != H2_FT_PRIORITY &&
h2c->dft != H2_FT_WINDOW_UPDATE) { h2c->dft != H2_FT_WINDOW_UPDATE) {
h2c_error(h2c, H2_ERR_STREAM_CLOSED); h2c_error(h2c, H2_ERR_STREAM_CLOSED);
goto strm_err; goto strm_err;
} }
skipping to change at line 2491 skipping to change at line 2436
case H2_FT_RST_STREAM: case H2_FT_RST_STREAM:
if (h2c->st0 == H2_CS_FRAME_P) if (h2c->st0 == H2_CS_FRAME_P)
ret = h2c_handle_rst_stream(h2c, h2s); ret = h2c_handle_rst_stream(h2c, h2s);
break; break;
case H2_FT_GOAWAY: case H2_FT_GOAWAY:
if (h2c->st0 == H2_CS_FRAME_P) if (h2c->st0 == H2_CS_FRAME_P)
ret = h2c_handle_goaway(h2c); ret = h2c_handle_goaway(h2c);
break; break;
case H2_FT_PUSH_PROMISE:
/* not permitted here, RFC7540#5.1 */
h2c_error(h2c, H2_ERR_PROTOCOL_ERROR);
if (!h2c->nb_streams) {
/* only log if no other stream can report the err
or */
sess_log(h2c->conn->owner);
}
break;
/* implement all extra frame types here */ /* implement all extra frame types here */
default: default:
/* drop frames that we ignore. They may be larger than /* drop frames that we ignore. They may be larger than
* the buffer so we drain all of their contents until * the buffer so we drain all of their contents until
* we reach the end. * we reach the end.
*/ */
ret = MIN(b_data(&h2c->dbuf), h2c->dfl); ret = MIN(b_data(&h2c->dbuf), h2c->dfl);
b_del(&h2c->dbuf, ret); b_del(&h2c->dbuf, ret);
h2c->dfl -= ret; h2c->dfl -= ret;
ret = h2c->dfl == 0; ret = h2c->dfl == 0;
skipping to change at line 2727 skipping to change at line 2663
*/ */
done = 0; done = 0;
while (!done) { while (!done) {
unsigned int flags = 0; unsigned int flags = 0;
/* fill as much as we can into the current buffer */ /* fill as much as we can into the current buffer */
while (((h2c->flags & (H2_CF_MUX_MFULL|H2_CF_MUX_MALLOC)) == 0) & & !done) while (((h2c->flags & (H2_CF_MUX_MFULL|H2_CF_MUX_MALLOC)) == 0) & & !done)
done = h2_process_mux(h2c); done = h2_process_mux(h2c);
if (h2c->flags & H2_CF_MUX_MALLOC)
break;
if (conn->flags & CO_FL_ERROR) if (conn->flags & CO_FL_ERROR)
break; break;
if (h2c->flags & (H2_CF_MUX_MFULL | H2_CF_DEM_MBUSY | H2_CF_DEM_M ROOM)) if (h2c->flags & (H2_CF_MUX_MFULL | H2_CF_DEM_MBUSY | H2_CF_DEM_M ROOM))
flags |= CO_SFL_MSG_MORE; flags |= CO_SFL_MSG_MORE;
if (b_data(&h2c->mbuf)) { if (b_data(&h2c->mbuf)) {
int ret = conn->xprt->snd_buf(conn, &h2c->mbuf, b_data(&h 2c->mbuf), flags); int ret = conn->xprt->snd_buf(conn, &h2c->mbuf, b_data(&h 2c->mbuf), flags);
if (!ret) if (!ret)
break; break;
skipping to change at line 3010 skipping to change at line 2949
struct session *sess; struct session *sess;
cs->ctx = NULL; cs->ctx = NULL;
if (!h2s) if (!h2s)
return; return;
sess = h2s->sess; sess = h2s->sess;
h2c = h2s->h2c; h2c = h2s->h2c;
h2s->cs = NULL; h2s->cs = NULL;
h2c->nb_cs--; h2c->nb_cs--;
if (h2c->flags & H2_CF_DEM_TOOMANY && if ((h2c->flags & (H2_CF_IS_BACK|H2_CF_DEM_TOOMANY)) == H2_CF_DEM_TOOMANY
!h2_has_too_many_cs(h2c)) { &&
!h2_frt_has_too_many_cs(h2c)) {
/* frontend connection was blocking new streams creation */
h2c->flags &= ~H2_CF_DEM_TOOMANY; h2c->flags &= ~H2_CF_DEM_TOOMANY;
h2c_restart_reading(h2c); h2c_restart_reading(h2c);
} }
/* this stream may be blocked waiting for some data to leave (possibly /* this stream may be blocked waiting for some data to leave (possibly
* an ES or RST frame), so orphan it in this case. * an ES or RST frame), so orphan it in this case.
*/ */
if (!(cs->conn->flags & CO_FL_ERROR) && if (!(cs->conn->flags & CO_FL_ERROR) &&
(h2c->st0 < H2_CS_ERROR) && (h2c->st0 < H2_CS_ERROR) &&
(h2s->flags & (H2_SF_BLK_MBUSY | H2_SF_BLK_MROOM | H2_SF_BLK_MFCTL))) (h2s->flags & (H2_SF_BLK_MBUSY | H2_SF_BLK_MROOM | H2_SF_BLK_MFCTL)))
skipping to change at line 3060 skipping to change at line 3000
} }
} }
if (eb_is_empty(&h2c->streams_by_id)) { if (eb_is_empty(&h2c->streams_by_id)) {
if (session_check_idle_conn(h2c->conn->owner, h2c ->conn) != 0) 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 */ /* At this point either the connection is destroyed, or it's been added to the server idle list, just stop */
return; return;
} }
/* Never ever allow to reuse a connection from a non-reus e backend */ /* Never ever allow to reuse a connection from a non-reus e backend */
if ((h2c->proxy->options & PR_O_REUSE_MASK) == PR_O_REUSE _NEVR) if ((h2c->proxy->options & PR_O_REUSE_MASK) == PR_O_REUSE _NEVR)
h2c->conn->flags |= CO_FL_PRIVATE; h2c->conn->flags |= CO_FL_PRIVATE;
if (LIST_ISEMPTY(&h2c->conn->list) && h2c->nb_streams < h 2_settings_max_concurrent_streams) { if (LIST_ISEMPTY(&h2c->conn->list) && h2c->nb_streams < h 2c->streams_limit) {
struct server *srv = objt_server(h2c->conn->targe t); struct server *srv = objt_server(h2c->conn->targe t);
if (srv) { if (srv) {
if (h2c->conn->flags & CO_FL_PRIVATE) if (h2c->conn->flags & CO_FL_PRIVATE)
LIST_ADD(&srv->priv_conns[tid], & h2c->conn->list); LIST_ADD(&srv->priv_conns[tid], & h2c->conn->list);
else else
LIST_ADD(&srv->idle_conns[tid], & h2c->conn->list); LIST_ADD(&srv->idle_conns[tid], & h2c->conn->list);
} }
} }
skipping to change at line 3082 skipping to change at line 3022
} }
/* We don't want to close right now unless we're removing the /* We don't want to close right now unless we're removing the
* last stream, and either the connection is in error, or it * last stream, and either the connection is in error, or it
* reached the ID already specified in a GOAWAY frame received * reached the ID already specified in a GOAWAY frame received
* or sent (as seen by last_sid >= 0). * or sent (as seen by last_sid >= 0).
*/ */
if (eb_is_empty(&h2c->streams_by_id) && /* don't close if streams exi st */ if (eb_is_empty(&h2c->streams_by_id) && /* don't close if streams exi st */
((h2c->conn->flags & CO_FL_ERROR) || /* errors close immediately * / ((h2c->conn->flags & CO_FL_ERROR) || /* errors close immediately * /
(h2c->st0 >= H2_CS_ERROR && !h2c->task) || /* a timeout stroke earli er */ (h2c->st0 >= H2_CS_ERROR && !h2c->task) || /* a timeout stroke earli er */
(h2c->flags & (H2_CF_GOAWAY_FAILED | H2_CF_GOAWAY_SENT)) ||
(!(h2c->conn->owner)) || /* Nobody's left to take care of the connec tion, drop it now */ (!(h2c->conn->owner)) || /* Nobody's left to take care of the connec tion, drop it now */
(!b_data(&h2c->mbuf) && /* mux buffer empty, also process clean eve nts below */ (!b_data(&h2c->mbuf) && /* mux buffer empty, also process clean eve nts below */
(conn_xprt_read0_pending(h2c->conn) || (conn_xprt_read0_pending(h2c->conn) ||
(h2c->last_sid >= 0 && h2c->max_id >= h2c->last_sid))))) { (h2c->last_sid >= 0 && h2c->max_id >= h2c->last_sid))))) {
/* no more stream will come, kill it now */ /* no more stream will come, kill it now */
h2_release(h2c->conn); h2_release(h2c->conn);
} }
else if (h2c->task) { else if (h2c->task) {
if (eb_is_empty(&h2c->streams_by_id) || b_data(&h2c->mbuf)) { if (eb_is_empty(&h2c->streams_by_id) || b_data(&h2c->mbuf)) {
h2c->task->expire = tick_add(now_ms, h2c->last_sid < 0 ? h2c->timeout : h2c->shut_timeout); h2c->task->expire = tick_add(now_ms, h2c->last_sid < 0 ? h2c->timeout : h2c->shut_timeout);
skipping to change at line 3108 skipping to change at line 3047
} }
static void h2_do_shutr(struct h2s *h2s) static void h2_do_shutr(struct h2s *h2s)
{ {
struct h2c *h2c = h2s->h2c; struct h2c *h2c = h2s->h2c;
struct wait_event *sw = &h2s->wait_event; struct wait_event *sw = &h2s->wait_event;
if (h2s->st == H2_SS_HLOC || h2s->st == H2_SS_ERROR || h2s->st == H2_SS_C LOSED) if (h2s->st == H2_SS_HLOC || h2s->st == H2_SS_ERROR || h2s->st == H2_SS_C LOSED)
return; return;
/* if no outgoing data was seen on this stream, it means it was /* a connstream may require us to immediately kill the whole connection
* closed with a "tcp-request content" rule that is normally * for example because of a "tcp-request content reject" rule that is
* used to kill the connection ASAP (eg: limit abuse). In this * normally used to limit abuse. In this case we schedule a goaway to
* case we send a goaway to close the connection. * close the connection.
*/ */
if ((h2s->cs && h2s->cs->flags & CS_FL_KILL_CONN) &&
!(h2c->flags & (H2_CF_GOAWAY_SENT|H2_CF_GOAWAY_FAILED))) {
h2c_error(h2c, H2_ERR_ENHANCE_YOUR_CALM);
h2s_error(h2s, H2_ERR_ENHANCE_YOUR_CALM);
}
if (!(h2s->flags & H2_SF_RST_SENT) && if (!(h2s->flags & H2_SF_RST_SENT) &&
h2s_send_rst_stream(h2c, h2s) <= 0) h2s_send_rst_stream(h2c, h2s) <= 0)
goto add_to_list; goto add_to_list;
if (!(h2s->flags & H2_SF_OUTGOING_DATA) &&
!(h2s->h2c->flags & (H2_CF_GOAWAY_SENT|H2_CF_GOAWAY_FAILED)) &&
h2c_send_goaway_error(h2c, h2s) <= 0)
return;
if (!(h2c->wait_event.events & SUB_RETRY_SEND)) if (!(h2c->wait_event.events & SUB_RETRY_SEND))
tasklet_wakeup(h2c->wait_event.task); tasklet_wakeup(h2c->wait_event.task);
h2s_close(h2s); h2s_close(h2s);
return; return;
add_to_list: add_to_list:
if (LIST_ISEMPTY(&h2s->list)) { if (LIST_ISEMPTY(&h2s->list)) {
sw->events |= SUB_RETRY_SEND; sw->events |= SUB_RETRY_SEND;
if (h2s->flags & H2_SF_BLK_MFCTL) { if (h2s->flags & H2_SF_BLK_MFCTL) {
LIST_ADDQ(&h2c->fctl_list, &h2s->list); LIST_ADDQ(&h2c->fctl_list, &h2s->list);
h2s->send_wait = sw; h2s->send_wait = sw;
} else if (h2s->flags & (H2_SF_BLK_MBUSY|H2_SF_BLK_MROOM)) { } else if (h2s->flags & (H2_SF_BLK_MBUSY|H2_SF_BLK_MROOM)) {
h2s->send_wait = sw; h2s->send_wait = sw;
LIST_ADDQ(&h2c->send_list, &h2s->list); LIST_ADDQ(&h2c->send_list, &h2s->list);
} }
} }
/* Let the handler know we want shutr */ /* Let the handler know we want shutr */
sw->handle = (void *)((long)sw->handle | 1); sw->handle = (void *)((long)sw->handle | 1);
} }
static void h2_do_shutw(struct h2s *h2s) static void h2_do_shutw(struct h2s *h2s)
{ {
struct h2c *h2c = h2s->h2c; struct h2c *h2c = h2s->h2c;
struct wait_event *sw = &h2s->wait_event; struct wait_event *sw = &h2s->wait_event;
if (h2s->st == H2_SS_HLOC || h2s->st == H2_SS_ERROR || h2s->st == H2_SS_C LOSED) if (h2s->st == H2_SS_HLOC || h2s->st == H2_SS_ERROR || h2s->st == H2_SS_C LOSED)
return; return;
skipping to change at line 3163 skipping to change at line 3102
if (!(h2s->flags & (H2_SF_ES_SENT|H2_SF_RST_SENT)) && if (!(h2s->flags & (H2_SF_ES_SENT|H2_SF_RST_SENT)) &&
h2_send_empty_data_es(h2s) <= 0) h2_send_empty_data_es(h2s) <= 0)
goto add_to_list; goto add_to_list;
if (h2s->st == H2_SS_HREM) if (h2s->st == H2_SS_HREM)
h2s_close(h2s); h2s_close(h2s);
else else
h2s->st = H2_SS_HLOC; h2s->st = H2_SS_HLOC;
} else { } else {
/* if no outgoing data was seen on this stream, it means it was /* a connstream may require us to immediately kill the whole conn
* closed with a "tcp-request content" rule that is normally ection
* used to kill the connection ASAP (eg: limit abuse). In this * for example because of a "tcp-request content reject" rule tha
* case we send a goaway to close the connection. t is
* normally used to limit abuse. In this case we schedule a goawa
y to
* close the connection.
*/ */
if ((h2s->cs && h2s->cs->flags & CS_FL_KILL_CONN) &&
!(h2c->flags & (H2_CF_GOAWAY_SENT|H2_CF_GOAWAY_FAILED))) {
h2c_error(h2c, H2_ERR_ENHANCE_YOUR_CALM);
h2s_error(h2s, H2_ERR_ENHANCE_YOUR_CALM);
}
if (!(h2s->flags & H2_SF_RST_SENT) && if (!(h2s->flags & H2_SF_RST_SENT) &&
h2s_send_rst_stream(h2c, h2s) <= 0) h2s_send_rst_stream(h2c, h2s) <= 0)
goto add_to_list; goto add_to_list;
if (!(h2s->flags & H2_SF_OUTGOING_DATA) &&
!(h2s->h2c->flags & (H2_CF_GOAWAY_SENT|H2_CF_GOAWAY_FAILED))
&&
h2c_send_goaway_error(h2c, h2s) <= 0)
goto add_to_list;
h2s_close(h2s); h2s_close(h2s);
} }
if (!(h2c->wait_event.events & SUB_RETRY_SEND)) if (!(h2c->wait_event.events & SUB_RETRY_SEND))
tasklet_wakeup(h2c->wait_event.task); tasklet_wakeup(h2c->wait_event.task);
return; return;
add_to_list: add_to_list:
if (LIST_ISEMPTY(&h2s->list)) { if (LIST_ISEMPTY(&h2s->list)) {
sw->events |= SUB_RETRY_SEND; sw->events |= SUB_RETRY_SEND;
if (h2s->flags & H2_SF_BLK_MFCTL) { if (h2s->flags & H2_SF_BLK_MFCTL) {
LIST_ADDQ(&h2c->fctl_list, &h2s->list); LIST_ADDQ(&h2c->fctl_list, &h2s->list);
h2s->send_wait = sw; h2s->send_wait = sw;
} else if (h2s->flags & (H2_SF_BLK_MBUSY|H2_SF_BLK_MROOM)) { } else if (h2s->flags & (H2_SF_BLK_MBUSY|H2_SF_BLK_MROOM)) {
h2s->send_wait = sw; h2s->send_wait = sw;
LIST_ADDQ(&h2c->send_list, &h2s->list); LIST_ADDQ(&h2c->send_list, &h2s->list);
} }
} }
/* let the handler know we want to shutw */ /* let the handler know we want to shutw */
sw->handle = (void *)((long)(sw->handle) | 2); sw->handle = (void *)((long)(sw->handle) | 2);
} }
static struct task *h2_deferred_shut(struct task *t, void *ctx, unsigned short s tate) static struct task *h2_deferred_shut(struct task *t, void *ctx, unsigned short s tate)
{ {
struct h2s *h2s = ctx; struct h2s *h2s = ctx;
long reason = (long)h2s->wait_event.handle; long reason = (long)h2s->wait_event.handle;
if (h2s->send_wait) { if (h2s->send_wait) {
h2s->send_wait->events &= ~SUB_CALL_UNSUBSCRIBE; h2s->send_wait->events &= ~SUB_CALL_UNSUBSCRIBE;
h2s->send_wait = NULL; h2s->send_wait = NULL;
skipping to change at line 3293 skipping to change at line 3232
* The <flags> field must point to either the stream's flags or to a copy of it * The <flags> field must point to either the stream's flags or to a copy of it
* so that the function can update the following flags : * so that the function can update the following flags :
* - H2_SF_DATA_CLEN when content-length is seen * - H2_SF_DATA_CLEN when content-length is seen
* - H2_SF_DATA_CHNK when chunking should be used for the H1 conversion * - H2_SF_DATA_CHNK when chunking should be used for the H1 conversion
* - H2_SF_HEADERS_RCVD once the frame is successfully decoded * - H2_SF_HEADERS_RCVD once the frame is successfully decoded
* *
* The H2_SF_HEADERS_RCVD flag is also looked at in the <flags> field prior to * 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 * 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).
*/ */
static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *f lags) static int h2c_decode_headers(struct h2c *h2c, struct buffer *rxbuf, uint32_t *f lags, unsigned long long *body_len)
{ {
const uint8_t *hdrs = (uint8_t *)b_head(&h2c->dbuf); const uint8_t *hdrs = (uint8_t *)b_head(&h2c->dbuf);
struct buffer *tmp = get_trash_chunk(); struct buffer *tmp = get_trash_chunk();
struct http_hdr list[MAX_HTTP_HDR * 2]; struct http_hdr list[MAX_HTTP_HDR * 2];
struct buffer *copy = NULL; struct buffer *copy = NULL;
unsigned int msgf; unsigned int msgf;
struct htx *htx = NULL; struct htx *htx = NULL;
int flen; // header frame len int flen; // header frame len
int hole = 0; int hole = 0;
int ret = 0; int ret = 0;
skipping to change at line 3453 skipping to change at line 3392
/* OK now we have our header list in <list> */ /* OK now we have our header list in <list> */
msgf = (h2c->dff & H2_F_DATA_END_STREAM) ? 0 : H2_MSGF_BODY; msgf = (h2c->dff & H2_F_DATA_END_STREAM) ? 0 : H2_MSGF_BODY;
if (*flags & H2_SF_HEADERS_RCVD) if (*flags & H2_SF_HEADERS_RCVD)
goto trailers; goto trailers;
/* This is the first HEADERS frame so it's a headers block */ /* This is the first HEADERS frame so it's a headers block */
if (htx) { if (htx) {
/* HTX mode */ /* HTX mode */
if (h2c->flags & H2_CF_IS_BACK) if (h2c->flags & H2_CF_IS_BACK)
outlen = h2_make_htx_response(list, htx, &msgf); outlen = h2_make_htx_response(list, htx, &msgf, body_len) ;
else else
outlen = h2_make_htx_request(list, htx, &msgf); outlen = h2_make_htx_request(list, htx, &msgf, body_len);
} else { } else {
/* HTTP/1 mode */ /* HTTP/1 mode */
outlen = h2_make_h1_request(list, b_tail(rxbuf), try, &msgf); outlen = h2_make_h1_request(list, b_tail(rxbuf), try, &msgf, body _len);
if (outlen > 0) if (outlen > 0)
b_add(rxbuf, outlen); b_add(rxbuf, outlen);
} }
if (outlen < 0) { if (outlen < 0) {
/* too large headers? this is a stream error only */ /* too large headers? this is a stream error only */
goto fail; goto fail;
} }
if (msgf & H2_MSGF_BODY) { if (msgf & H2_MSGF_BODY) {
skipping to change at line 3603 skipping to change at line 3542
htx = htx_from_buf(csbuf); htx = htx_from_buf(csbuf);
if (!flen) if (!flen)
goto end_transfer; goto end_transfer;
if (flen > b_data(&h2c->dbuf)) { if (flen > b_data(&h2c->dbuf)) {
flen = b_data(&h2c->dbuf); flen = b_data(&h2c->dbuf);
if (!flen) if (!flen)
goto fail; goto fail;
} }
if (h2c->proxy->options2 & PR_O2_USE_HTX) { if (htx) {
block1 = htx_free_data_space(htx); block1 = htx_free_data_space(htx);
if (!block1) { if (!block1) {
h2c->flags |= H2_CF_DEM_SFULL; h2c->flags |= H2_CF_DEM_SFULL;
goto fail; goto fail;
} }
if (flen > block1) if (flen > block1)
flen = block1; flen = block1;
/* here, flen is the max we can copy into the output buffer */ /* here, flen is the max we can copy into the output buffer */
block1 = b_contig_data(&h2c->dbuf, 0); block1 = b_contig_data(&h2c->dbuf, 0);
skipping to change at line 3626 skipping to change at line 3565
if (!htx_add_data(htx, ist2(b_head(&h2c->dbuf), flen))) { if (!htx_add_data(htx, ist2(b_head(&h2c->dbuf), flen))) {
h2c->flags |= H2_CF_DEM_SFULL; h2c->flags |= H2_CF_DEM_SFULL;
goto fail; goto fail;
} }
b_del(&h2c->dbuf, flen); b_del(&h2c->dbuf, flen);
h2c->dfl -= flen; h2c->dfl -= flen;
h2c->rcvd_c += flen; h2c->rcvd_c += flen;
h2c->rcvd_s += flen; // warning, this can also affect the closed streams! h2c->rcvd_s += flen; // warning, this can also affect the closed streams!
if (h2s->flags & H2_SF_DATA_CLEN)
h2s->body_len -= flen;
goto try_again; goto try_again;
} }
else if (unlikely(b_space_wraps(csbuf))) { else if (unlikely(b_space_wraps(csbuf))) {
/* it doesn't fit and the buffer is fragmented, /* it doesn't fit and the buffer is fragmented,
* so let's defragment it and try again. * so let's defragment it and try again.
*/ */
b_slow_realign(csbuf, trash.area, 0); b_slow_realign(csbuf, trash.area, 0);
} }
/* chunked-encoding requires more room */ /* chunked-encoding requires more room */
skipping to change at line 3695 skipping to change at line 3637
} }
/* now mark the input data as consumed (will be deleted from the buffer /* now mark the input data as consumed (will be deleted from the buffer
* by the caller when seeing FRAME_A after sending the window update). * by the caller when seeing FRAME_A after sending the window update).
*/ */
b_del(&h2c->dbuf, flen); b_del(&h2c->dbuf, flen);
h2c->dfl -= flen; h2c->dfl -= flen;
h2c->rcvd_c += flen; h2c->rcvd_c += flen;
h2c->rcvd_s += flen; // warning, this can also affect the closed streams ! h2c->rcvd_s += flen; // warning, this can also affect the closed streams !
if (h2s->flags & H2_SF_DATA_CLEN)
h2s->body_len -= flen;
if (h2c->dfl > h2c->dpl) { if (h2c->dfl > h2c->dpl) {
/* more data available, transfer stalled on stream full */ /* more data available, transfer stalled on stream full */
h2c->flags |= H2_CF_DEM_SFULL; h2c->flags |= H2_CF_DEM_SFULL;
goto fail; goto fail;
} }
end_transfer: end_transfer:
/* here we're done with the frame, all the payload (except padding) was /* here we're done with the frame, all the payload (except padding) was
* transferred. * transferred.
*/ */
skipping to change at line 4399 skipping to change at line 4344
* emitted and the resulting htx message could be left in an inconsistent state. * emitted and the resulting htx message could be left in an inconsistent state.
*/ */
static size_t h2s_htx_bck_make_req_headers(struct h2s *h2s, struct htx *htx) static size_t h2s_htx_bck_make_req_headers(struct h2s *h2s, struct htx *htx)
{ {
struct http_hdr list[MAX_HTTP_HDR]; struct http_hdr list[MAX_HTTP_HDR];
struct h2c *h2c = h2s->h2c; struct h2c *h2c = h2s->h2c;
struct htx_blk *blk; struct htx_blk *blk;
struct htx_blk *blk_end; struct htx_blk *blk_end;
struct buffer outbuf; struct buffer outbuf;
struct htx_sl *sl; struct htx_sl *sl;
struct ist meth, path; struct ist meth, path, auth;
enum htx_blk_type type; enum htx_blk_type type;
int es_now = 0; int es_now = 0;
int ret = 0; int ret = 0;
int hdr; int hdr;
int idx; int idx;
if (h2c_mux_busy(h2c, h2s)) { if (h2c_mux_busy(h2c, h2s)) {
h2s->flags |= H2_SF_BLK_MBUSY; h2s->flags |= H2_SF_BLK_MBUSY;
return 0; return 0;
} }
skipping to change at line 4491 skipping to change at line 4436
write_n32(outbuf.area + 5, h2s->id); // 4 bytes write_n32(outbuf.area + 5, h2s->id); // 4 bytes
outbuf.data = 9; outbuf.data = 9;
/* encode the method, which necessarily is the first one */ /* encode the method, which necessarily is the first one */
if (!hpack_encode_method(&outbuf, sl->info.req.meth, meth)) { if (!hpack_encode_method(&outbuf, sl->info.req.meth, meth)) {
if (b_space_wraps(&h2c->mbuf)) if (b_space_wraps(&h2c->mbuf))
goto realign_again; goto realign_again;
goto full; goto full;
} }
/* encode the scheme which is always "https" (or 0x86 for "http") */ /* RFC7540 #8.3: the CONNECT method must have :
if (!hpack_encode_scheme(&outbuf, ist("https"))) { * - :authority set to the URI part (host:port)
/* output full */ * - :method set to CONNECT
if (b_space_wraps(&h2c->mbuf)) * - :scheme and :path omitted
goto realign_again; */
goto full; if (sl->info.req.meth != HTTP_METH_CONNECT) {
/* encode the scheme which is always "https" (or 0x86 for "http")
*/
if (!hpack_encode_scheme(&outbuf, ist("https"))) {
/* output full */
if (b_space_wraps(&h2c->mbuf))
goto realign_again;
goto full;
}
/* encode the path, which necessarily is the second one */
if (!hpack_encode_path(&outbuf, path)) {
/* output full */
if (b_space_wraps(&h2c->mbuf))
goto realign_again;
goto full;
}
/* look for the Host header and place it in :authority */
auth = ist2(NULL, 0);
for (hdr = 0; hdr < sizeof(list)/sizeof(list[0]); hdr++) {
if (isteq(list[hdr].n, ist("")))
break; // end
if (isteq(list[hdr].n, ist("host"))) {
auth = list[hdr].v;
break;
}
}
}
else {
/* for CONNECT, :authority is taken from the path */
auth = path;
} }
/* encode the path, which necessarily is the second one */ if (auth.ptr && !hpack_encode_header(&outbuf, ist(":authority"), auth)) {
if (!hpack_encode_path(&outbuf, path)) {
/* output full */ /* output full */
if (b_space_wraps(&h2c->mbuf)) if (b_space_wraps(&h2c->mbuf))
goto realign_again; goto realign_again;
goto full; goto full;
} }
/* encode all headers, stop at empty name */ /* encode all headers, stop at empty name */
for (hdr = 0; hdr < sizeof(list)/sizeof(list[0]); hdr++) { for (hdr = 0; hdr < sizeof(list)/sizeof(list[0]); hdr++) {
/* these ones do not exist in H2 and must be dropped. */ /* these ones do not exist in H2 and must be dropped. */
if (isteq(list[hdr].n, ist("connection")) || if (isteq(list[hdr].n, ist("connection")) ||
isteq(list[hdr].n, ist("host")) ||
isteq(list[hdr].n, ist("proxy-connection")) || isteq(list[hdr].n, ist("proxy-connection")) ||
isteq(list[hdr].n, ist("keep-alive")) || isteq(list[hdr].n, ist("keep-alive")) ||
isteq(list[hdr].n, ist("upgrade")) || isteq(list[hdr].n, ist("upgrade")) ||
isteq(list[hdr].n, ist("transfer-encoding"))) isteq(list[hdr].n, ist("transfer-encoding")))
continue; continue;
if (isteq(list[hdr].n, ist(""))) if (isteq(list[hdr].n, ist("")))
break; // end break; // end
if (!hpack_encode_header(&outbuf, list[hdr].n, list[hdr].v)) { if (!hpack_encode_header(&outbuf, list[hdr].n, list[hdr].v)) {
skipping to change at line 5500 skipping to change at line 5476
/* config parser for global "tune.h2.max-concurrent-streams" */ /* config parser for global "tune.h2.max-concurrent-streams" */
static int h2_parse_max_concurrent_streams(char **args, int section_type, struct proxy *curpx, static int h2_parse_max_concurrent_streams(char **args, int section_type, struct proxy *curpx,
struct proxy *defpx, const char *file , int line, struct proxy *defpx, const char *file , int line,
char **err) char **err)
{ {
if (too_many_args(1, args, err, NULL)) if (too_many_args(1, args, err, NULL))
return -1; return -1;
h2_settings_max_concurrent_streams = atoi(args[1]); h2_settings_max_concurrent_streams = atoi(args[1]);
if (h2_settings_max_concurrent_streams < 0) { if ((int)h2_settings_max_concurrent_streams < 0) {
memprintf(err, "'%s' expects a positive numeric value.", args[0]) ; memprintf(err, "'%s' expects a positive numeric value.", args[0]) ;
return -1; return -1;
} }
return 0; return 0;
} }
/****************************************/ /****************************************/
/* MUX initialization and instanciation */ /* MUX initialization and instanciation */
/***************************************/ /***************************************/
skipping to change at line 5524 skipping to change at line 5500
.wake = h2_wake, .wake = h2_wake,
.snd_buf = h2_snd_buf, .snd_buf = h2_snd_buf,
.rcv_buf = h2_rcv_buf, .rcv_buf = h2_rcv_buf,
.subscribe = h2_subscribe, .subscribe = h2_subscribe,
.unsubscribe = h2_unsubscribe, .unsubscribe = h2_unsubscribe,
.attach = h2_attach, .attach = h2_attach,
.get_first_cs = h2_get_first_cs, .get_first_cs = h2_get_first_cs,
.detach = h2_detach, .detach = h2_detach,
.destroy = h2_destroy, .destroy = h2_destroy,
.avail_streams = h2_avail_streams, .avail_streams = h2_avail_streams,
.max_streams = h2_max_streams, .used_streams = h2_used_streams,
.shutr = h2_shutr, .shutr = h2_shutr,
.shutw = h2_shutw, .shutw = h2_shutw,
.show_fd = h2_show_fd, .show_fd = h2_show_fd,
.flags = MX_FL_CLEAN_ABRT, .flags = MX_FL_CLEAN_ABRT,
.name = "H2", .name = "H2",
}; };
/* PROTO selection : this mux registers PROTO token "h2" */ /* PROTO selection : this mux registers PROTO token "h2" */
static struct mux_proto_list mux_proto_h2 = static struct mux_proto_list mux_proto_h2 =
{ .token = IST("h2"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_FE, .mu x = &h2_ops }; { .token = IST("h2"), .mode = PROTO_MODE_HTTP, .side = PROTO_SIDE_FE, .mu x = &h2_ops };
 End of changes. 86 change blocks. 
232 lines changed or deleted 217 lines changed or added

Home  |  About  |  Features  |  All  |  Newest  |  Dox  |  Diffs  |  RSS Feeds  |  Screenshots  |  Comments  |  Imprint  |  Privacy  |  HTTP(S)