Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

h2: Rapid reset mitigations (7.4) #4009

Merged
merged 18 commits into from
Oct 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions bin/varnishd/cache/cache_transport.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ typedef void vtr_sess_panic_f (struct vsb *, const struct sess *);
typedef void vtr_req_panic_f (struct vsb *, const struct req *);
typedef void vtr_req_fail_f (struct req *, stream_close_t);
typedef void vtr_reembark_f (struct worker *, struct req *);
typedef int vtr_poll_f (struct req *);
typedef int vtr_minimal_response_f (struct req *, uint16_t status);

struct transport {
Expand All @@ -64,6 +65,7 @@ struct transport {
vtr_sess_panic_f *sess_panic;
vtr_req_panic_f *req_panic;
vtr_reembark_f *reembark;
vtr_poll_f *poll;
vtr_minimal_response_f *minimal_response;

VTAILQ_ENTRY(transport) list;
Expand Down
37 changes: 37 additions & 0 deletions bin/varnishd/cache/cache_vrt_vcl.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
#include "vbm.h"

#include "cache_director.h"
#include "cache_transport.h"
#include "cache_vcl.h"
#include "vcc_interface.h"

Expand Down Expand Up @@ -521,6 +522,40 @@ VRT_VCL_Allow_Discard(struct vclref **refp)
FREE_OBJ(ref);
}

/*--------------------------------------------------------------------
*/

static int
req_poll(struct worker *wrk, struct req *req)
{
struct req *top;

/* NB: Since a fail transition leads to vcl_synth, the request may be
* short-circuited twice.
*/
if (req->req_reset) {
wrk->vpi->handling = VCL_RET_FAIL;
return (-1);
}

top = req->top->topreq;
CHECK_OBJ_NOTNULL(top, REQ_MAGIC);
CHECK_OBJ_NOTNULL(top->transport, TRANSPORT_MAGIC);

if (!FEATURE(FEATURE_VCL_REQ_RESET))
return (0);
if (top->transport->poll == NULL)
return (0);
if (top->transport->poll(top) >= 0)
return (0);

VSLb_ts_req(req, "Reset", W_TIM_real(wrk));
wrk->stats->req_reset++;
wrk->vpi->handling = VCL_RET_FAIL;
req->req_reset = 1;
return (-1);
}

/*--------------------------------------------------------------------
* Method functions to call into VCL programs.
*
Expand Down Expand Up @@ -552,6 +587,8 @@ vcl_call_method(struct worker *wrk, struct req *req, struct busyobj *bo,
CHECK_OBJ_NOTNULL(req->sp, SESS_MAGIC);
CHECK_OBJ_NOTNULL(req->vcl, VCL_MAGIC);
CHECK_OBJ_NOTNULL(req->top, REQTOP_MAGIC);
if (req_poll(wrk, req))
return;
VCL_Req2Ctx(&ctx, req);
}
assert(ctx.now != 0);
Expand Down
10 changes: 10 additions & 0 deletions bin/varnishd/http2/cache_http2.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ struct h2_error_s {

typedef const struct h2_error_s *h2_error;

#define H2_CUSTOM_ERRORS
#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)
Expand Down Expand Up @@ -193,6 +194,15 @@ struct h2_sess {
VTAILQ_HEAD(,h2_req) txqueue;

h2_error error;

// rst rate limit parameters, copied from h2_* parameters
vtim_dur rapid_reset;
int64_t rapid_reset_limit;
vtim_dur rapid_reset_period;

// rst rate limit state
double rst_budget;
vtim_real last_rst;
};

#define ASSERT_RXTHR(h2) do {assert(h2->rxthr == pthread_self());} while(0)
Expand Down
41 changes: 40 additions & 1 deletion bin/varnishd/http2/cache_http2_proto.c
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
#include "vtcp.h"
#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)
Expand Down Expand Up @@ -320,9 +321,46 @@ h2_rx_push_promise(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
/**********************************************************************
*/

static h2_error
h2_rapid_reset(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
{
vtim_real now;
vtim_dur d;

CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
ASSERT_RXTHR(h2);
CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);

if (h2->rapid_reset_limit == 0)
return (0);

now = VTIM_real();
CHECK_OBJ_NOTNULL(r2->req, REQ_MAGIC);
AN(r2->req->t_first);
if (now - r2->req->t_first > h2->rapid_reset)
return (0);

d = now - h2->last_rst;
h2->rst_budget += h2->rapid_reset_limit * d /
h2->rapid_reset_period;
h2->rst_budget = vmin_t(double, h2->rst_budget,
h2->rapid_reset_limit);
h2->last_rst = now;

if (h2->rst_budget < 1.0) {
Lck_Lock(&h2->sess->mtx);
VSLb(h2->vsl, SLT_Error, "H2: Hit RST limit. Closing session.");
Lck_Unlock(&h2->sess->mtx);
return (H2CE_RAPID_RESET);
}
h2->rst_budget -= 1.0;
return (0);
}

static h2_error v_matchproto_(h2_rxframe_f)
h2_rx_rst_stream(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
{
h2_error h2e;

CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
ASSERT_RXTHR(h2);
Expand All @@ -332,8 +370,9 @@ h2_rx_rst_stream(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
return (H2CE_FRAME_SIZE_ERROR);
if (r2 == NULL)
return (0);
h2e = h2_rapid_reset(wrk, h2, r2);
h2_kill_req(wrk, h2, r2, h2_streamerror(vbe32dec(h2->rxf_data)));
return (0);
return (h2e);
}

/**********************************************************************
Expand Down
19 changes: 19 additions & 0 deletions bin/varnishd/http2/cache_http2_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,14 @@ h2_init_sess(struct sess *sp,
h2->remote_settings = H2_proto_settings;
h2->decode = decode;

h2->rapid_reset = cache_param->h2_rapid_reset;
h2->rapid_reset_limit = cache_param->h2_rapid_reset_limit;
h2->rapid_reset_period = cache_param->h2_rapid_reset_period;

h2->rst_budget = h2->rapid_reset_limit;
h2->last_rst = sp->t_open;
AZ(isnan(h2->last_rst));

AZ(VHT_Init(h2->dectbl, h2->local_settings.header_table_size));

*up = (uintptr_t)h2;
Expand Down Expand Up @@ -438,6 +446,16 @@ h2_new_session(struct worker *wrk, void *arg)
wrk->vsl = NULL;
}

static int v_matchproto_(vtr_poll_f)
h2_poll(struct req *req)
{
struct h2_req *r2;

CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
CAST_OBJ_NOTNULL(r2, req->transport_priv, H2_REQ_MAGIC);
return (r2->error ? -1 : 1);
}

struct transport HTTP2_transport = {
.name = "HTTP/2",
.magic = TRANSPORT_MAGIC,
Expand All @@ -447,4 +465,5 @@ struct transport HTTP2_transport = {
.req_body = h2_req_body,
.req_fail = h2_req_fail,
.sess_panic = h2_sess_panic,
.poll = h2_poll,
};
90 changes: 90 additions & 0 deletions bin/varnishtest/tests/r03996.vtc
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
varnishtest "h2 rapid reset"

barrier b1 sock 2 -cyclic
barrier b2 sock 5 -cyclic

server s1 {
rxreq
txresp
} -start

varnish v1 -cliok "param.set feature +http2"
varnish v1 -cliok "param.set debug +syncvsl"
varnish v1 -cliok "param.set h2_rapid_reset_limit 3"
varnish v1 -cliok "param.set h2_rapid_reset 5"

varnish v1 -vcl+backend {
import vtc;

sub vcl_recv {
if (req.http.barrier) {
vtc.barrier_sync(req.http.barrier);
}
vtc.barrier_sync("${b2_sock}");
}

} -start

client c1 {
stream 0 {
rxgoaway
expect goaway.err == ENHANCE_YOUR_CALM
} -start

stream 1 {
txreq -hdr barrier ${b1_sock}
barrier b1 sync
txrst
} -run
stream 3 {
txreq -hdr barrier ${b1_sock}
barrier b1 sync
txrst
} -run
stream 5 {
txreq -hdr barrier ${b1_sock}
barrier b1 sync
txrst
} -run
stream 7 {
txreq -hdr barrier ${b1_sock}
barrier b1 sync
txrst
} -run

barrier b2 sync
stream 0 -wait
} -run

varnish v1 -expect sc_rapid_reset == 1

varnish v1 -cliok "param.set feature -vcl_req_reset"

client c2 {
stream 0 {
rxgoaway
expect goaway.err == ENHANCE_YOUR_CALM
} -start

stream 1 {
txreq
txrst
} -run
stream 3 {
txreq
txrst
} -run
stream 5 {
txreq
txrst
} -run
stream 7 {
txreq
txrst
} -run

barrier b2 sync
stream 0 -wait
} -run

varnish v1 -expect sc_rapid_reset == 2
Loading