varnish/SOURCES/varnish-6.6.2-CVE-2024-30156.patch

1064 lines
29 KiB
Diff
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

diff --git a/bin/varnishd/VSC_main.vsc b/bin/varnishd/VSC_main.vsc
index 0978c2f..a9a1431 100644
--- a/bin/varnishd/VSC_main.vsc
+++ b/bin/varnishd/VSC_main.vsc
@@ -648,6 +648,14 @@
configured limits for the number of permitted rapid stream
resets.
+.. varnish_vsc:: sc_bankrupt
+ :level: diag
+ :oneliner: Session Err BANKRUPT
+
+ Number of times we failed an http/2 session because all the streams
+ were waiting for their windows to be credited when h2_window_timeout
+ triggered.
+
.. varnish_vsc:: client_resp_500
:level: diag
:group: wrk
diff --git a/bin/varnishd/http2/cache_http2.h b/bin/varnishd/http2/cache_http2.h
index 9088e21..78d7824 100644
--- a/bin/varnishd/http2/cache_http2.h
+++ b/bin/varnishd/http2/cache_http2.h
@@ -44,15 +44,17 @@ struct h2_error_s {
uint32_t val;
int stream;
int connection;
+ int send_goaway;
enum sess_close reason;
};
typedef const struct h2_error_s *h2_error;
-#define H2EC1(U,v,r,d) extern const struct h2_error_s H2CE_##U[1];
-#define H2EC2(U,v,r,d) extern const struct h2_error_s H2SE_##U[1];
-#define H2EC3(U,v,r,d) H2EC1(U,v,r,d) H2EC2(U,v,r,d)
-#define H2_ERROR(NAME, val, sc, reason, desc) H2EC##sc(NAME, val, reason, desc)
+#define H2EC1(U,v,g,r,d) extern const struct h2_error_s H2CE_##U[1];
+#define H2EC2(U,v,g,r,d) extern const struct h2_error_s H2SE_##U[1];
+#define H2EC3(U,v,g,r,d) H2EC1(U,v,g,r,d) H2EC2(U,v,g,r,d)
+#define H2_ERROR(NAME, val, sc, goaway, reason, desc) \
+ H2EC##sc(NAME, val, goaway, reason, desc)
#include "tbl/h2_error.h"
#undef H2EC1
#undef H2EC2
@@ -153,8 +155,10 @@ struct h2_sess {
struct sess *sess;
int refcnt;
- unsigned open_streams;
+ int open_streams;
+ int winup_streams;
uint32_t highest_stream;
+ int goaway;
int bogosity;
int do_sweep;
@@ -238,7 +242,7 @@ void H2_Send(struct worker *, struct h2_req *, h2_frame type, uint8_t flags,
/* cache_http2_proto.c */
struct h2_req * h2_new_req(const struct worker *, struct h2_sess *,
unsigned stream, struct req *);
-int h2_stream_tmo(struct h2_sess *, const struct h2_req *, vtim_real);
+h2_error h2_stream_tmo(struct h2_sess *, const struct h2_req *, vtim_real);
void h2_del_req(struct worker *, const struct h2_req *);
void h2_kill_req(struct worker *, struct h2_sess *, struct h2_req *, h2_error);
int h2_rxframe(struct worker *, struct h2_sess *);
diff --git a/bin/varnishd/http2/cache_http2_proto.c b/bin/varnishd/http2/cache_http2_proto.c
index 408acad..d4e04ce 100644
--- a/bin/varnishd/http2/cache_http2_proto.c
+++ b/bin/varnishd/http2/cache_http2_proto.c
@@ -46,10 +46,13 @@
#include "vtim.h"
#define H2_CUSTOM_ERRORS
-#define H2EC1(U,v,r,d) const struct h2_error_s H2CE_##U[1] = {{#U,d,v,0,1,r}};
-#define H2EC2(U,v,r,d) const struct h2_error_s H2SE_##U[1] = {{#U,d,v,1,0,r}};
-#define H2EC3(U,v,r,d) H2EC1(U,v,r,d) H2EC2(U,v,r,d)
-#define H2_ERROR(NAME, val, sc, reason, desc) H2EC##sc(NAME, val, reason, desc)
+#define H2EC1(U,v,g,r,d) \
+ const struct h2_error_s H2CE_##U[1] = {{#U,d,v,0,1,g,r}};
+#define H2EC2(U,v,g,r,d) \
+ const struct h2_error_s H2SE_##U[1] = {{#U,d,v,1,0,g,r}};
+#define H2EC3(U,v,g,r,d) H2EC1(U,v,g,r,d) H2EC2(U,v,g,r,d)
+#define H2_ERROR(NAME, val, sc, goaway, reason, desc) \
+ H2EC##sc(NAME, val, goaway, reason, desc)
#include "tbl/h2_error.h"
#undef H2EC1
#undef H2EC2
@@ -61,6 +64,7 @@ static const struct h2_error_s H2NN_ERROR[1] = {{
0xffffffff,
1,
1,
+ 0,
SC_RX_JUNK
}};
@@ -88,10 +92,11 @@ h2_framename(enum h2frame h2f)
*/
static const h2_error stream_errors[] = {
-#define H2EC1(U,v,r,d)
-#define H2EC2(U,v,r,d) [v] = H2SE_##U,
-#define H2EC3(U,v,r,d) H2EC1(U,v,r,d) H2EC2(U,v,r,d)
-#define H2_ERROR(NAME, val, sc, reason, desc) H2EC##sc(NAME, val, reason, desc)
+#define H2EC1(U,v,g,r,d)
+#define H2EC2(U,v,g,r,d) [v] = H2SE_##U,
+#define H2EC3(U,v,g,r,d) H2EC1(U,v,g,r,d) H2EC2(U,v,g,r,d)
+#define H2_ERROR(NAME, val, sc, goaway, reason, desc) \
+ H2EC##sc(NAME, val, goaway, reason, desc)
#include "tbl/h2_error.h"
#undef H2EC1
#undef H2EC2
@@ -113,10 +118,11 @@ h2_streamerror(uint32_t u)
*/
static const h2_error conn_errors[] = {
-#define H2EC1(U,v,r,d) [v] = H2CE_##U,
-#define H2EC2(U,v,r,d)
-#define H2EC3(U,v,r,d) H2EC1(U,v,r,d) H2EC2(U,v,r,d)
-#define H2_ERROR(NAME, val, sc, reason, desc) H2EC##sc(NAME, val, reason, desc)
+#define H2EC1(U,v,g,r,d) [v] = H2CE_##U,
+#define H2EC2(U,v,g,r,d)
+#define H2EC3(U,v,g,r,d) H2EC1(U,v,g,r,d) H2EC2(U,v,g,r,d)
+#define H2_ERROR(NAME, val, sc, goaway, reason, desc) \
+ H2EC##sc(NAME, val, goaway, reason, desc)
#include "tbl/h2_error.h"
#undef H2EC1
#undef H2EC2
@@ -371,6 +377,7 @@ h2_rx_goaway(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
assert(r2 == h2->req0);
+ h2->goaway = 1;
h2->goaway_last_stream = vbe32dec(h2->rxf_data);
h2->error = h2_connectionerror(vbe32dec(h2->rxf_data + 4));
Lck_Lock(&h2->sess->mtx);
@@ -379,6 +386,25 @@ h2_rx_goaway(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
return (h2->error);
}
+static void
+h2_tx_goaway(struct worker *wrk, struct h2_sess *h2, h2_error h2e)
+{
+ char b[8];
+
+ ASSERT_RXTHR(h2);
+ AN(h2e);
+
+ if (h2->goaway || !h2e->send_goaway)
+ return;
+
+ h2->goaway = 1;
+ vbe32enc(b, h2->highest_stream);
+ vbe32enc(b + 4, h2e->val);
+ H2_Send_Get(wrk, h2, h2->req0);
+ H2_Send_Frame(wrk, h2, H2_F_GOAWAY, 0, 8, 0, b);
+ H2_Send_Rel(h2, h2->req0);
+}
+
/**********************************************************************
*/
@@ -799,9 +825,9 @@ h2_rx_data(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
Lck_Lock(&h2->sess->mtx);
while (h2->mailcall != NULL && h2->error == 0 && r2->error == 0)
AZ(Lck_CondWait(h2->cond, &h2->sess->mtx, 0));
- if (h2->error || r2->error) {
+ if (h2->error != NULL || r2->error != NULL) {
Lck_Unlock(&h2->sess->mtx);
- return (h2->error ? h2->error : r2->error);
+ return (h2->error != NULL ? h2->error : r2->error);
}
r2->reqbody_bytes += h2->rxf_len;
@@ -879,7 +905,7 @@ h2_vfp_body(struct vfp_ctx *vc, struct vfp_entry *vfe, void *ptr, ssize_t *lp)
while (h2->mailcall != r2 && h2->error == 0 && r2->error == 0)
AZ(Lck_CondWait(r2->cond, &h2->sess->mtx, 0));
r2->cond = NULL;
- if (h2->error || r2->error) {
+ if (h2->error != NULL || r2->error != NULL) {
retval = VFP_ERROR;
} else {
assert(h2->mailcall == r2);
@@ -1013,82 +1039,91 @@ h2_procframe(struct worker *wrk, struct h2_sess *h2, h2_frame h2f)
if (r2->stream == h2->rxf_stream)
break;
- if (h2->new_req != NULL &&
- !(r2 && h2->new_req == r2->req && h2f == H2_F_CONTINUATION))
+ if (h2->new_req != NULL && h2f != H2_F_CONTINUATION)
return (H2CE_PROTOCOL_ERROR); // rfc7540,l,1859,1863
h2e = h2f->rxfunc(wrk, h2, r2);
- if (h2e == 0)
- return (0);
+ if (h2e == NULL)
+ return (NULL);
if (h2->rxf_stream == 0 || h2e->connection)
return (h2e); // Connection errors one level up
H2_Send_Get(wrk, h2, h2->req0);
H2_Send_RST(wrk, h2, h2->req0, h2->rxf_stream, h2e);
H2_Send_Rel(h2, h2->req0);
- return (0);
+ return (NULL);
}
-int
+h2_error
h2_stream_tmo(struct h2_sess *h2, const struct h2_req *r2, vtim_real now)
{
- int r = 0;
+ h2_error h2e = NULL;
CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
Lck_AssertHeld(&h2->sess->mtx);
- /* NB: when now is NAN, it means that idle_send_timeout was hit
+ /* NB: when now is NAN, it means that h2_window_timeout was hit
* on a lock condwait operation.
*/
if (isnan(now))
AN(r2->t_winupd);
-
+
+ if (h2->error != NULL && h2->error->connection &&
+ !h2->error->send_goaway)
+ return (h2->error);
+
if (r2->t_winupd == 0 && r2->t_send == 0)
- return (0);
+ return (NULL);
if (isnan(now) || (r2->t_winupd != 0 &&
- now - r2->t_winupd > SESS_TMO(h2->sess, idle_send_timeout))) {
+ now - r2->t_winupd > cache_param->h2_window_timeout)) {
VSLb(h2->vsl, SLT_Debug,
- "H2: stream %u: Hit idle_send_timeout waiting for"
- " WINDOW_UPDATE", r2->stream);
- r = 1;
+ "H2: stream %u: Hit h2_window_timeout", r2->stream);
+ h2e = H2SE_BROKE_WINDOW;
}
- if (r == 0 && r2->t_send != 0 &&
+ if (h2e == NULL && r2->t_send != 0 &&
now - r2->t_send > SESS_TMO(h2->sess, send_timeout)) {
VSLb(h2->vsl, SLT_Debug,
"H2: stream %u: Hit send_timeout", r2->stream);
- r = 1;
+ h2e = H2SE_CANCEL;
}
- return (r);
+ return (h2e);
}
-static int
+static h2_error
h2_stream_tmo_unlocked(struct h2_sess *h2, const struct h2_req *r2)
{
- int r;
+ h2_error h2e;
Lck_Lock(&h2->sess->mtx);
- r = h2_stream_tmo(h2, r2, h2->sess->t_idle);
+ h2e = h2_stream_tmo(h2, r2, h2->sess->t_idle);
Lck_Unlock(&h2->sess->mtx);
- return (r);
+ return (h2e);
}
/*
* This is the janitorial task of cleaning up any closed & refused
* streams, and checking if the session is timed out.
*/
-static int
+static h2_error
h2_sweep(struct worker *wrk, struct h2_sess *h2)
{
- int tmo = 0;
struct h2_req *r2, *r22;
+ h2_error h2e, tmo;
+ vtim_real now;
ASSERT_RXTHR(h2);
+ h2e = h2->error;
+ now = VTIM_real();
+ if (h2e == NULL && h2->open_streams == 0 &&
+ h2->sess->t_idle + cache_param->timeout_idle < now)
+ h2e = H2CE_NO_ERROR;
+
h2->do_sweep = 0;
VTAILQ_FOREACH_SAFE(r2, &h2->streams, list, r22) {
if (r2 == h2->req0) {
@@ -1112,10 +1147,9 @@ h2_sweep(struct worker *wrk, struct h2_sess *h2)
/* FALLTHROUGH */
case H2_S_CLOS_LOC:
case H2_S_OPEN:
- if (h2_stream_tmo_unlocked(h2, r2)) {
- tmo = 1;
- continue;
- }
+ tmo = h2_stream_tmo_unlocked(h2, r2);
+ if (h2e == NULL)
+ h2e = tmo;
break;
case H2_S_IDLE:
/* Current code make this unreachable: h2_new_req is
@@ -1127,9 +1161,7 @@ h2_sweep(struct worker *wrk, struct h2_sess *h2)
break;
}
}
- if (tmo)
- return (0);
- return (h2->refcnt > 1);
+ return (h2e);
}
@@ -1154,34 +1186,38 @@ h2_rxframe(struct worker *wrk, struct h2_sess *h2)
enum htc_status_e hs;
h2_frame h2f;
h2_error h2e;
- char b[8];
ASSERT_RXTHR(h2);
+
+ if (h2->goaway && h2->open_streams == 0)
+ return (0);
+
VTCP_blocking(*h2->htc->rfd);
- h2->sess->t_idle = VTIM_real();
- hs = HTC_RxStuff(h2->htc, h2_frame_complete,
- NULL, NULL, NAN,
- h2->sess->t_idle + SESS_TMO(h2->sess, timeout_idle),
- NAN, h2->local_settings.max_frame_size + 9);
+ hs = HTC_RxStuff(h2->htc, h2_frame_complete, NULL, NULL, NAN,
+ VTIM_real() + 0.5, NAN, h2->local_settings.max_frame_size + 9);
+
+ h2e = NULL;
switch (hs) {
case HTC_S_COMPLETE:
+ h2->sess->t_idle = VTIM_real();
+ if (h2->do_sweep)
+ h2e = h2_sweep(wrk, h2);
break;
case HTC_S_TIMEOUT:
- if (h2_sweep(wrk, h2))
- return (1);
-
- /* FALLTHROUGH */
+ h2e = h2_sweep(wrk, h2);
+ break;
default:
- /* XXX: HTC_S_OVERFLOW / FRAME_SIZE_ERROR handling */
- Lck_Lock(&h2->sess->mtx);
- VSLb(h2->vsl, SLT_Debug, "H2: No frame (hs=%d)", hs);
- h2->error = H2CE_NO_ERROR;
- Lck_Unlock(&h2->sess->mtx);
+ h2e = H2CE_ENHANCE_YOUR_CALM;
+ }
+
+ if (h2e != NULL && h2e->connection) {
+ h2->error = h2e;
+ h2_tx_goaway(wrk, h2, h2e);
return (0);
}
- if (h2->do_sweep)
- (void)h2_sweep(wrk, h2);
+ if (hs != HTC_S_COMPLETE)
+ return (1);
h2->rxf_len = vbe32dec(h2->htc->rxbuf_b) >> 8;
h2->rxf_type = h2->htc->rxbuf_b[3];
@@ -1226,13 +1262,10 @@ h2_rxframe(struct worker *wrk, struct h2_sess *h2)
}
h2e = h2_procframe(wrk, h2, h2f);
- if (h2->error == 0 && h2e) {
+ if (h2->error == NULL && h2e != NULL) {
h2->error = h2e;
- vbe32enc(b, h2->highest_stream);
- vbe32enc(b + 4, h2e->val);
- H2_Send_Get(wrk, h2, h2->req0);
- H2_Send_Frame(wrk, h2, H2_F_GOAWAY, 0, 8, 0, b);
- H2_Send_Rel(h2, h2->req0);
+ h2_tx_goaway(wrk, h2, h2e);
}
- return (h2->error ? 0 : 1);
+
+ return (h2->error != NULL ? 0 : 1);
}
diff --git a/bin/varnishd/http2/cache_http2_send.c b/bin/varnishd/http2/cache_http2_send.c
index a684b94..9043901 100644
--- a/bin/varnishd/http2/cache_http2_send.c
+++ b/bin/varnishd/http2/cache_http2_send.c
@@ -43,10 +43,24 @@
#define H2_SEND_HELD(h2, r2) (VTAILQ_FIRST(&(h2)->txqueue) == (r2))
+static h2_error
+h2_errcheck(const struct h2_req *r2, const struct h2_sess *h2)
+{
+ CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
+ CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
+
+ if (r2->error != NULL)
+ return (r2->error);
+ if (h2->error != NULL && r2->stream > h2->goaway_last_stream)
+ return (h2->error);
+ return (NULL);
+}
+
static int
h2_cond_wait(pthread_cond_t *cond, struct h2_sess *h2, struct h2_req *r2)
{
vtim_real now, when = 0.;
+ h2_error h2e;
int r;
AN(cond);
@@ -56,8 +70,8 @@ h2_cond_wait(pthread_cond_t *cond, struct h2_sess *h2, struct h2_req *r2)
Lck_AssertHeld(&h2->sess->mtx);
now = VTIM_real();
- if (cache_param->idle_send_timeout > 0.)
- when = now + cache_param->idle_send_timeout;
+ if (cache_param->h2_window_timeout > 0.)
+ when = now + cache_param->h2_window_timeout;
r = Lck_CondWait(cond, &h2->sess->mtx, when);
assert(r == 0 || r == ETIMEDOUT);
@@ -66,24 +80,23 @@ h2_cond_wait(pthread_cond_t *cond, struct h2_sess *h2, struct h2_req *r2)
/* NB: when we grab idle_send_timeout before acquiring the session
* lock we may time out, but once we wake up both send_timeout and
- * idle_send_timeout may have changed meanwhile. For this reason
+ * h2_window_timeout may have changed meanwhile. For this reason
* h2_stream_tmo() may not log what timed out and we need to call
* again with a magic NAN "now" that indicates to h2_stream_tmo()
- * that the stream reached the idle_send_timeout via the lock and
+ * that the stream reached the h2_window_timeout via the lock and
* force it to log it.
*/
- if (h2_stream_tmo(h2, r2, now))
- r = ETIMEDOUT;
- else if (r == ETIMEDOUT)
- AN(h2_stream_tmo(h2, r2, NAN));
-
- if (r == ETIMEDOUT) {
- if (r2->error == NULL)
- r2->error = H2SE_CANCEL;
- return (-1);
+ h2e = h2_stream_tmo(h2, r2, now);
+
+ if (h2e == NULL && r == ETIMEDOUT) {
+ h2e = h2_stream_tmo(h2, r2, NAN);
+ AN(h2e);
}
- return (0);
+ if (r2->error == NULL)
+ r2->error = h2e;
+
+ return (h2e != NULL ? -1 : 0);
}
static void
@@ -196,6 +209,10 @@ H2_Send_Frame(struct worker *wrk, struct h2_sess *h2,
iov[1].iov_len = len;
s = writev(h2->sess->fd, iov, len == 0 ? 1 : 2);
if (s != sizeof hdr + len) {
+ if (errno == EWOULDBLOCK) {
+ VSLb(h2->vsl, SLT_Debug,
+ "H2: stream %u: Hit idle_send_timeout", stream);
+ }
/*
* There is no point in being nice here, we will be unable
* to send a GOAWAY once the code unrolls, so go directly
@@ -237,19 +254,6 @@ h2_win_charge(struct h2_req *r2, const struct h2_sess *h2, uint32_t w)
h2->req0->t_window -= w;
}
-static h2_error
-h2_errcheck(const struct h2_req *r2, const struct h2_sess *h2)
-{
- CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
- CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
-
- if (r2->error)
- return (r2->error);
- if (h2->error && r2->stream > h2->goaway_last_stream)
- return (h2->error);
- return (0);
-}
-
static int64_t
h2_do_window(struct worker *wrk, struct h2_req *r2,
struct h2_sess *h2, int64_t wanted)
@@ -267,25 +271,35 @@ h2_do_window(struct worker *wrk, struct h2_req *r2,
if (r2->t_window <= 0 || h2->req0->t_window <= 0) {
r2->t_winupd = VTIM_real();
h2_send_rel_locked(h2, r2);
- while (r2->t_window <= 0 && h2_errcheck(r2, h2) == 0) {
+
+ assert(h2->winup_streams >= 0);
+ h2->winup_streams++;
+
+ while (r2->t_window <= 0 && h2_errcheck(r2, h2) == NULL) {
r2->cond = &wrk->cond;
(void)h2_cond_wait(r2->cond, h2, r2);
r2->cond = NULL;
}
- while (h2->req0->t_window <= 0 && h2_errcheck(r2, h2) == 0)
+ while (h2->req0->t_window <= 0 && h2_errcheck(r2, h2) == NULL)
(void)h2_cond_wait(h2->winupd_cond, h2, r2);
- if (h2_errcheck(r2, h2) == 0) {
- w = h2_win_limit(r2, h2);
- if (w > wanted)
- w = wanted;
+ if (h2_errcheck(r2, h2) == NULL) {
+ w = vmin_t(int64_t, h2_win_limit(r2, h2), wanted);
h2_win_charge(r2, h2, w);
assert (w > 0);
}
+
+ if (r2->error == H2SE_BROKE_WINDOW &&
+ h2->open_streams <= h2->winup_streams)
+ h2->error = r2->error = H2CE_BANKRUPT;
+
+ assert(h2->winup_streams > 0);
+ h2->winup_streams--;
+
h2_send_get_locked(wrk, h2, r2);
}
- if (w == 0 && h2_errcheck(r2, h2) == 0) {
+ if (w == 0 && h2_errcheck(r2, h2) == NULL) {
assert(r2->t_window > 0);
assert(h2->req0->t_window > 0);
w = h2_win_limit(r2, h2);
@@ -322,7 +336,7 @@ h2_send(struct worker *wrk, struct h2_req *r2, h2_frame ftyp, uint8_t flags,
AN(H2_SEND_HELD(h2, r2));
- if (h2_errcheck(r2, h2))
+ if (h2_errcheck(r2, h2) != NULL)
return;
AN(ftyp);
@@ -368,7 +382,7 @@ h2_send(struct worker *wrk, struct h2_req *r2, h2_frame ftyp, uint8_t flags,
if (ftyp->respect_window && p != ptr) {
tf = h2_do_window(wrk, r2, h2,
(len > mfs) ? mfs : len);
- if (h2_errcheck(r2, h2))
+ if (h2_errcheck(r2, h2) != NULL)
return;
AN(H2_SEND_HELD(h2, r2));
}
@@ -389,7 +403,7 @@ h2_send(struct worker *wrk, struct h2_req *r2, h2_frame ftyp, uint8_t flags,
ftyp = ftyp->continuation;
flags &= ftyp->flags;
final_flags &= ftyp->flags;
- } while (!h2->error && len > 0);
+ } while (h2->error == NULL && len > 0);
}
}
@@ -402,6 +416,7 @@ H2_Send_RST(struct worker *wrk, struct h2_sess *h2, const struct h2_req *r2,
CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
AN(H2_SEND_HELD(h2, r2));
+ AN(h2e);
Lck_Lock(&h2->sess->mtx);
VSLb(h2->vsl, SLT_Debug, "H2: stream %u: %s", stream, h2e->txt);
@@ -416,12 +431,15 @@ H2_Send(struct worker *wrk, struct h2_req *r2, h2_frame ftyp, uint8_t flags,
uint32_t len, const void *ptr, uint64_t *counter)
{
uint64_t dummy_counter;
+ h2_error h2e;
if (counter == NULL)
counter = &dummy_counter;
h2_send(wrk, r2, ftyp, flags, len, ptr, counter);
- if (h2_errcheck(r2, r2->h2sess) == H2SE_CANCEL)
- H2_Send_RST(wrk, r2->h2sess, r2, r2->stream, H2SE_CANCEL);
+ h2e = h2_errcheck(r2, r2->h2sess);
+ if (h2e != NULL && h2e->val == H2SE_CANCEL->val)
+ H2_Send_RST(wrk, r2->h2sess, r2, r2->stream, h2e);
+
}
diff --git a/bin/varnishtest/tests/t02003.vtc b/bin/varnishtest/tests/t02003.vtc
index 1d62ac9..fe30e82 100644
--- a/bin/varnishtest/tests/t02003.vtc
+++ b/bin/varnishtest/tests/t02003.vtc
@@ -233,17 +233,28 @@ client c1 {
} -run
client c1 {
+ stream 0 {
+ rxgoaway
+ expect goaway.err == NO_ERROR
+ expect goaway.laststream == 3
+ } -start
stream 1 {
- txreq -nohdrend
+ txreq -nostrend
txrst -err 2
} -run
stream 3 {
- txreq -nohdrend
+ txreq -nostrend
txrst -err 0x666
} -run
+ stream 0 -wait
} -run
client c1 {
+ stream 0 {
+ rxgoaway
+ expect goaway.err == NO_ERROR
+ expect goaway.laststream == 1
+ } -start
stream 1 {
txreq
rxresp
@@ -252,6 +263,7 @@ client c1 {
# RST_STREAM on closed stream
txrst
} -run
+ stream 0 -wait
} -run
diff --git a/bin/varnishtest/tests/t02005.vtc b/bin/varnishtest/tests/t02005.vtc
index 03c9a85..9aff481 100644
--- a/bin/varnishtest/tests/t02005.vtc
+++ b/bin/varnishtest/tests/t02005.vtc
@@ -27,7 +27,7 @@ varnish v1 -cliok "param.set debug +syncvsl"
logexpect l1 -v v1 -g raw {
expect * 1001 ReqAcct "80 7 87 106 8 114"
- expect * 1000 ReqAcct "45 8 53 54 20 74"
+ expect * 1000 ReqAcct "45 8 53 63 28 91"
} -start
client c1 {
diff --git a/bin/varnishtest/tests/t02016.vtc b/bin/varnishtest/tests/t02016.vtc
index 29ffea3..1e5a7dc 100644
--- a/bin/varnishtest/tests/t02016.vtc
+++ b/bin/varnishtest/tests/t02016.vtc
@@ -6,7 +6,13 @@ server s1 {
} -start
varnish v1 -cliok "param.set feature +http2"
-varnish v1 -vcl+backend "" -start
+varnish v1 -vcl+backend {
+ sub vcl_recv {
+ if (req.url ~ "synth") {
+ return (synth(200));
+ }
+ }
+} -start
# coverage for send_timeout with c1
@@ -44,13 +50,13 @@ client c1 {
logexpect l1 -wait
-# coverage for idle_send_timeout with c2
+# coverage for h2_window_timeout with c2
-varnish v1 -cliok "param.set idle_send_timeout 1"
+varnish v1 -cliok "param.set h2_window_timeout 1"
varnish v1 -cliok "param.reset send_timeout"
logexpect l2 -v v1 {
- expect * * Debug "Hit idle_send_timeout"
+ expect * * Debug "Hit h2_window_timeout"
} -start
client c2 {
@@ -66,6 +72,10 @@ client c2 {
} -run
stream 1 {
+ txreq -nostrend -url "/synth"
+ } -run
+
+ stream 3 {
txreq
rxhdrs
rxdata
@@ -75,16 +85,19 @@ client c2 {
expect rst.err == CANCEL
} -run
+ stream 1 {
+ txdata
+ } -run
} -run
logexpect l2 -wait
-# coverage for idle_send_timeout change with c3
+# coverage for h2_window_timeout change with c3
barrier b3 cond 2
logexpect l3 -v v1 {
- expect * * Debug "Hit idle_send_timeout"
+ expect * * Debug "Hit h2_window_timeout"
} -start
client c3 {
@@ -100,6 +113,10 @@ client c3 {
} -run
stream 1 {
+ txreq -nostrend -url "/synth"
+ } -run
+
+ stream 3 {
txreq
rxhdrs
rxdata
@@ -110,10 +127,13 @@ client c3 {
expect rst.err == CANCEL
} -run
+ stream 1 {
+ txdata
+ } -run
} -start
barrier b3 sync
-varnish v1 -cliok "param.reset idle_send_timeout"
+varnish v1 -cliok "param.reset h2_window_timeout"
client c3 -wait
logexpect l3 -wait
diff --git a/bin/varnishtest/tests/t02025.vtc b/bin/varnishtest/tests/t02025.vtc
index 3b7e90e..9a502a7 100644
--- a/bin/varnishtest/tests/t02025.vtc
+++ b/bin/varnishtest/tests/t02025.vtc
@@ -25,11 +25,17 @@ logexpect l1 -v v1 -g raw -i Debug {
} -start
client c1 {
+ stream 0 {
+ rxgoaway
+ expect goaway.err == NO_ERROR
+ expect goaway.laststream == 1
+ } -start
stream 1 {
txreq
barrier b1 sync
txrst
} -run
+ stream 0 -wait
} -start
logexpect l1 -wait
diff --git a/bin/varnishtest/vtc_http2.c b/bin/varnishtest/vtc_http2.c
index be80d32..23ffc5d 100644
--- a/bin/varnishtest/vtc_http2.c
+++ b/bin/varnishtest/vtc_http2.c
@@ -52,7 +52,7 @@
#define BUF_SIZE (1024*2048)
static const char *const h2_errs[] = {
-#define H2_ERROR(n,v,sc,r,t) [v] = #n,
+#define H2_ERROR(n,v,sc,g,r,t) [v] = #n,
#include <tbl/h2_error.h>
NULL
};
@@ -1228,7 +1228,7 @@ cmd_var_resolve(const struct stream *s, const char *spec, char *buf)
else
return (NULL);
}
-#define H2_ERROR(U,v,sc,r,t) \
+#define H2_ERROR(U,v,sc,g,r,t) \
if (!strcmp(spec, #U)) { return (#v); }
#include "tbl/h2_error.h"
return (spec);
diff --git a/include/tbl/h2_error.h b/include/tbl/h2_error.h
index 11051de..ef34caa 100644
--- a/include/tbl/h2_error.h
+++ b/include/tbl/h2_error.h
@@ -39,6 +39,7 @@ H2_ERROR(
/* name */ NO_ERROR,
/* val */ 0,
/* types */ 3,
+ /* goaway */ 1,
/* reason */ SC_REM_CLOSE,
/* descr */ "Graceful shutdown"
)
@@ -47,6 +48,7 @@ H2_ERROR(
/* name */ PROTOCOL_ERROR,
/* val */ 1,
/* types */ 3,
+ /* goaway */ 1,
/* reason */ SC_RX_JUNK,
/* descr */ "Protocol error detected"
)
@@ -55,6 +57,7 @@ H2_ERROR(
/* name */ INTERNAL_ERROR,
/* val */ 2,
/* types */ 3,
+ /* goaway */ 1,
/* reason */ SC_VCL_FAILURE,
/* descr */ "Implementation fault"
)
@@ -63,6 +66,7 @@ H2_ERROR(
/* name */ FLOW_CONTROL_ERROR,
/* val */ 3,
/* types */ 3,
+ /* goaway */ 1,
/* reason */ SC_OVERLOAD,
/* descr */ "Flow-control limits exceeded"
)
@@ -71,6 +75,7 @@ H2_ERROR(
/* name */ SETTINGS_TIMEOUT,
/* val */ 4,
/* types */ 1,
+ /* goaway */ 1,
/* reason */ SC_RX_TIMEOUT,
/* descr */ "Settings not acknowledged"
)
@@ -79,6 +84,7 @@ H2_ERROR(
/* name */ STREAM_CLOSED,
/* val */ 5,
/* types */ 2,
+ /* goaway */ 1,
/* reason */ SC_NULL,
/* descr */ "Frame received for closed stream"
)
@@ -87,6 +93,7 @@ H2_ERROR(
/* name */ FRAME_SIZE_ERROR,
/* val */ 6,
/* types */ 3,
+ /* goaway */ 1,
/* reason */ SC_RX_JUNK,
/* descr */ "Frame size incorrect"
)
@@ -95,6 +102,7 @@ H2_ERROR(
/* name */ REFUSED_STREAM,
/* val */ 7,
/* types */ 2,
+ /* goaway */ 1,
/* reason */ SC_NULL,
/* descr */ "Stream not processed"
)
@@ -103,6 +111,7 @@ H2_ERROR(
/* name */ CANCEL,
/* val */ 8,
/* types */ 2,
+ /* goaway */ 1,
/* reason */ SC_NULL,
/* descr */ "Stream cancelled"
)
@@ -111,6 +120,7 @@ H2_ERROR(
/* name */ COMPRESSION_ERROR,
/* val */ 9,
/* types */ 1,
+ /* goaway */ 1,
/* reason */ SC_RX_JUNK,
/* descr */ "Compression state not updated"
)
@@ -119,6 +129,7 @@ H2_ERROR(
/* name */ CONNECT_ERROR,
/* val */ 10,
/* types */ 2,
+ /* goaway */ 1,
/* reason */ SC_NULL,
/* descr */ "TCP connection error for CONNECT method"
)
@@ -127,6 +138,7 @@ H2_ERROR(
/* name */ ENHANCE_YOUR_CALM,
/* val */ 11,
/* types */ 3,
+ /* goaway */ 1,
/* reason */ SC_OVERLOAD,
/* descr */ "Processing capacity exceeded"
)
@@ -135,6 +147,7 @@ H2_ERROR(
/* name */ INADEQUATE_SECURITY,
/* val */ 12,
/* types */ 1,
+ /* goaway */ 1,
/* reason */ SC_RX_JUNK,
/* descr */ "Negotiated TLS parameters not acceptable"
)
@@ -143,6 +156,7 @@ H2_ERROR(
/* name */ HTTP_1_1_REQUIRED,
/* val */ 13,
/* types */ 1,
+ /* goaway */ 1,
/* reason */ SC_REQ_HTTP20,
/* descr */ "Use HTTP/1.1 for the request"
)
@@ -152,10 +166,28 @@ H2_ERROR(
/* name */ RAPID_RESET,
/* val */ 11, /* ENHANCE_YOUR_CALM */
/* types */ 1,
+ /* goaway */ 1,
/* reason */ SC_RAPID_RESET,
/* descr */ "http/2 rapid reset detected"
)
+H2_ERROR(
+ /* name */ BROKE_WINDOW,
+ /* val */ 8, /* CANCEL */
+ /* types */ 2,
+ /* goaway */ 0,
+ /* reason */ SC_NULL,
+ /* descr */ "http/2 stream out of window credits"
+)
+
+H2_ERROR(
+ /* name */ BANKRUPT,
+ /* val */ 11, /* ENHANCE_YOUR_CALM */
+ /* types */ 1,
+ /* goaway */ 0,
+ /* reason */ SC_BANKRUPT,
+ /* descr */ "http/2 bankrupt connection"
+)
# undef H2_CUSTOM_ERRORS
#endif
diff --git a/include/tbl/params.h b/include/tbl/params.h
index 4014dd6..49e2cdd 100644
--- a/include/tbl/params.h
+++ b/include/tbl/params.h
@@ -309,7 +309,7 @@ PARAM_SIMPLE(
/* type */ bytes_u,
/* min */ "128b",
/* max */ "99999999b",
- /* def */ "48k",
+ /* def */ "64k",
/* units */ "bytes",
/* descr */
"Maximum size of CLI response. If the response exceeds this "
@@ -1158,6 +1158,20 @@ PARAM_SIMPLE(
/* flags */ WIZARD
)
+PARAM_SIMPLE(
+ /* name */ h2_window_timeout,
+ /* type */ timeout,
+ /* min */ "0",
+ /* max */ NULL,
+ /* def */ "5",
+ /* units */ "seconds",
+ /* descr */
+ "HTTP2 time limit without window credits. How long a stream may "
+ "wait for the client to credit the window and allow for more DATA "
+ "frames to be sent.",
+ /* flags */ WIZARD
+)
+
PARAM_SIMPLE(
/* name */ h2_header_table_size,
/* type */ bytes_u,
diff --git a/include/tbl/sess_close.h b/include/tbl/sess_close.h
index 6d2f635..ef54760 100644
--- a/include/tbl/sess_close.h
+++ b/include/tbl/sess_close.h
@@ -51,6 +51,7 @@ SESS_CLOSE(RANGE_SHORT, range_short, 1, "Insufficient data for range")
SESS_CLOSE(REQ_HTTP20, req_http20, 1, "HTTP2 not accepted")
SESS_CLOSE(VCL_FAILURE, vcl_failure, 1, "VCL failure")
SESS_CLOSE(RAPID_RESET, rapid_reset, 1, "HTTP2 rapid reset")
+SESS_CLOSE(BANKRUPT, bankrupt, 1, "HTTP2 credit bankruptcy")
#undef SESS_CLOSE
/*lint -restore */
diff --git a/bin/varnishd/http2/cache_http2.h b/bin/varnishd/http2/cache_http2.h
index 78d7824..a36bf14 100644
--- a/bin/varnishd/http2/cache_http2.h
+++ b/bin/varnishd/http2/cache_http2.h
@@ -50,6 +50,7 @@ struct h2_error_s {
typedef const struct h2_error_s *h2_error;
+#define H2_CUSTOM_ERRORS
#define H2EC1(U,v,g,r,d) extern const struct h2_error_s H2CE_##U[1];
#define H2EC2(U,v,g,r,d) extern const struct h2_error_s H2SE_##U[1];
#define H2EC3(U,v,g,r,d) H2EC1(U,v,g,r,d) H2EC2(U,v,g,r,d)
diff --git a/bin/varnishtest/tests/t02025.vtc b/bin/varnishtest/tests/t02025.vtc
index 9a502a7..5e02b13 100644
--- a/bin/varnishtest/tests/t02025.vtc
+++ b/bin/varnishtest/tests/t02025.vtc
@@ -49,7 +49,7 @@ varnish v1 -expect req_reset == 1
# is interpreted as before a second elapsed. Session VXIDs showing up
# numerous times become increasingly more suspicious. The format can of
# course be extended to add anything else useful for data mining.
-shell -expect "1000 ${localhost}" {
+shell -expect "1000 ${localhost} 408" {
varnishncsa -n ${v1_name} -d \
- -q 'Timestamp:Reset[2] < 1.0' -F '%{VSL:Begin[2]}x %h'
+ -q 'Timestamp:Reset[2] < 1.0' -F '%{VSL:Begin[2]}x %h %s'
}
diff --git a/bin/varnishtest/tests/t02025.vtc b/bin/varnishtest/tests/t02025.vtc
index 5e02b13..39f987a 100644
--- a/bin/varnishtest/tests/t02025.vtc
+++ b/bin/varnishtest/tests/t02025.vtc
@@ -41,6 +41,8 @@ client c1 {
logexpect l1 -wait
barrier b2 sync
+client c1 -wait
+
varnish v1 -vsl_catchup
varnish v1 -expect req_reset == 1
diff --git a/bin/varnishd/cache/cache_req_fsm.c b/bin/varnishd/cache/cache_req_fsm.c
index 24eac15..42b0b5f 100644
--- a/bin/varnishd/cache/cache_req_fsm.c
+++ b/bin/varnishd/cache/cache_req_fsm.c
@@ -279,8 +279,13 @@ cnt_vclfail(struct worker *wrk, struct req *req)
Req_Rollback(ctx);
- req->err_code = 503;
- req->err_reason = "VCL failed";
+ if (req->req_reset) {
+ req->err_code = 408;
+ req->err_reason = "Client disconnected";
+ } else {
+ req->err_code = 503;
+ req->err_reason = "VCL failed";
+ }
req->req_step = R_STP_SYNTH;
req->doclose = SC_VCL_FAILURE;
req->filter_list = NULL;
diff --git a/doc/sphinx/reference/vsl.rst b/doc/sphinx/reference/vsl.rst
index f1ed987..845f50b 100644
--- a/doc/sphinx/reference/vsl.rst
+++ b/doc/sphinx/reference/vsl.rst
@@ -77,9 +77,10 @@ Restart
Client request is being restarted.
Reset
- The client closed its connection, reset its stream or caused
- a stream error that forced Varnish to reset the stream. Request
- processing is interrupted and considered failed.
+ The client closed its connection, reset its stream or caused
+ a stream error that forced Varnish to reset the stream. Request
+ processing is interrupted and considered failed, with a 408
+ "Request Timeout" status code.
Pipe handling timestamps
~~~~~~~~~~~~~~~~~~~~~~~~