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

Add vmod_h2 to control http rapid reset rate limits per session #3999

Merged
merged 2 commits into from
Oct 18, 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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 7 additions & 0 deletions bin/varnishd/http2/cache_http2.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,13 @@ 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;
};
Expand Down
10 changes: 5 additions & 5 deletions bin/varnishd/http2/cache_http2_proto.c
Original file line number Diff line number Diff line change
Expand Up @@ -331,20 +331,20 @@ h2_rapid_reset(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
ASSERT_RXTHR(h2);
CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);

if (cache_param->h2_rapid_reset_limit == 0)
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 > cache_param->h2_rapid_reset)
if (now - r2->req->t_first > h2->rapid_reset)
return (0);

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

if (h2->rst_budget < 1.0) {
Expand Down
7 changes: 6 additions & 1 deletion bin/varnishd/http2/cache_http2_session.c
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,12 @@ h2_init_sess(struct sess *sp,
h2_local_settings(&h2->local_settings);
h2->remote_settings = H2_proto_settings;
h2->decode = decode;
h2->rst_budget = cache_param->h2_rapid_reset_limit;

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));

Expand Down
16 changes: 10 additions & 6 deletions include/tbl/params.h
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,10 @@ PARAM_SIMPLE(
"HTTP2 maximum size of an uncompressed header list."
)

#define H2_RR_INFO \
"Changes to this parameter affect the default for new HTTP2 " \
"sessions. vmod_h2(3) can be used to adjust it from VCL."

PARAM_SIMPLE(
/* name */ h2_rapid_reset,
/* typ */ timeout,
Expand All @@ -1268,8 +1272,8 @@ PARAM_SIMPLE(
"The upper threshold for how soon an http/2 RST_STREAM frame has "
"to be parsed after a HEADERS frame for it to be treated as "
"suspect and subjected to the rate limits specified by "
"h2_rapid_reset_limit and h2_rapid_reset_period.",
/* flags */ EXPERIMENTAL,
"h2_rapid_reset_limit and h2_rapid_reset_period.\n" H2_RR_INFO,
/* flags */ EXPERIMENTAL|DELAYED_EFFECT,
)

PARAM_SIMPLE(
Expand All @@ -1283,8 +1287,8 @@ PARAM_SIMPLE(
"HTTP2 RST Allowance.\n"
"Specifies the maximum number of allowed stream resets issued by\n"
"a client over a time period before the connection is closed.\n"
"Setting this parameter to 0 disables the limit.",
/* flags */ EXPERIMENTAL,
"Setting this parameter to 0 disables the limit.\n" H2_RR_INFO,
/* flags */ EXPERIMENTAL|DELAYED_EFFECT,
)

PARAM_SIMPLE(
Expand All @@ -1295,8 +1299,8 @@ PARAM_SIMPLE(
/* def */ "60.000",
/* units */ "seconds",
/* descr */
"HTTP2 sliding window duration for h2_rapid_reset_limit.",
/* flags */ EXPERIMENTAL|WIZARD,
"HTTP2 sliding window duration for h2_rapid_reset_limit.\n" H2_RR_INFO,
/* flags */ EXPERIMENTAL|DELAYED_EFFECT|WIZARD,
)

/*--------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions vmod/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ vmod_vcc_files =
vmod_debug_vcc =

include $(srcdir)/automake_boilerplate_blob.am
include $(srcdir)/automake_boilerplate_h2.am
include $(srcdir)/automake_boilerplate_cookie.am
include $(srcdir)/automake_boilerplate_debug.am
include $(srcdir)/automake_boilerplate_directors.am
Expand Down
42 changes: 42 additions & 0 deletions vmod/automake_boilerplate_h2.am
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Generated by vmodtool.py --boilerplate.

vmod_h2_vcc ?= $(srcdir)/vmod_h2.vcc

vmod_vcc_files += $(vmod_h2_vcc)

vmod_LTLIBRARIES += libvmod_h2.la

libvmod_h2_la_SOURCES = \
vmod_h2.c

libvmod_h2_la_CFLAGS =

vmodtoolargs_h2 ?= --strict --boilerplate -o vcc_h2_if
vmod_h2_symbols_regex ?= Vmod_h2_Data

libvmod_h2_la_LDFLAGS = \
-export-symbols-regex $(vmod_h2_symbols_regex) \
$(AM_LDFLAGS) \
$(VMOD_LDFLAGS)

nodist_libvmod_h2_la_SOURCES = vcc_h2_if.c vcc_h2_if.h

EXTRA_libvmod_h2_la_DEPENDENCIES = $(nodist_libvmod_h2_la_SOURCES)

EXTRA_DIST += automake_boilerplate_h2.am

$(libvmod_h2_la_OBJECTS): vcc_h2_if.h

vcc_h2_if.h vmod_h2.rst vmod_h2.man.rst: vcc_h2_if.c

# A doc-change will not update mtime on the .h and .c files, so a
# touch(1) is necessary to signal that vmodtool was in fact run.
vcc_h2_if.c: $(VMODTOOL) $(srcdir)/vmod_h2.vcc
@PYTHON@ $(VMODTOOL) $(vmodtoolargs_h2) $(srcdir)/vmod_h2.vcc
touch vcc_h2_if.c

clean-local: clean-vmod-h2

clean-vmod-h2:
rm -f $(nodist_libvmod_h2_la_SOURCES)
rm -f vmod_h2.rst vmod_h2.man.rst
90 changes: 90 additions & 0 deletions vmod/tests/h2_b00000.vtc
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
varnishtest "VMOD h2 basics"

varnish v1 -arg "-p feature=+http2" -vcl {
import h2;

backend proforma none;

sub vcl_recv {
return(synth(200));
}

sub vcl_synth {
set resp.http.http2-is = h2.is();
set resp.body = "";
return (deliver);
}
} -start

client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.http2-is == false
} -start

client c2 {
stream 7 {
txreq
rxresp
expect resp.status == 200
expect resp.http.http2-is == true
} -run
} -start

client c1 -wait
client c2 -wait

# coverage
varnish v1 -vcl {
import h2;

backend proforma none;

sub vcl_recv {
return(synth(200));
}

sub vcl_synth {
set resp.http.rapid-reset-o = h2.rapid_reset(10ms);
set resp.http.rapid-reset-n = h2.rapid_reset();
set resp.http.rapid-reset-limit-o = h2.rapid_reset_limit(100);
set resp.http.rapid-reset-limit-n = h2.rapid_reset_limit();
set resp.http.rapid-reset-period-o = h2.rapid_reset_period(10s);
set resp.http.rapid-reset-period-n = h2.rapid_reset_period();
set resp.http.rapid-reset-budget = h2.rapid_reset_budget();
set resp.body = "";
return (deliver);
}
}

client c1 {
txreq
rxresp
expect resp.status == 200
expect resp.http.rapid-reset-o == -1.000
expect resp.http.rapid-reset-n == -1.000
expect resp.http.rapid-reset-limit-o == -1
expect resp.http.rapid-reset-limit-n == -1
expect resp.http.rapid-reset-period-o == -1.000
expect resp.http.rapid-reset-period-n == -1.000
expect resp.http.rapid-reset-budget == -1.000
} -start

client c2 {
stream 7 {
txreq
rxresp
expect resp.status == 200
expect resp.http.rapid-reset-o == 1.000
expect resp.http.rapid-reset-n == 0.010
expect resp.http.rapid-reset-limit-o == 0
expect resp.http.rapid-reset-limit-n == 100
expect resp.http.rapid-reset-period-o == 60.000
expect resp.http.rapid-reset-period-n == 10.000
expect resp.http.rapid-reset-budget == 100.000
} -run
} -start

client c1 -wait
client c2 -wait
100 changes: 100 additions & 0 deletions vmod/vmod_h2.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*-
* Copyright 2023 UPLEX - Nils Goroll Systemoptimierung
* All rights reserved.
*
* Author: Nils Goroll <nils.goroll@uplex.de>
*
* SPDX-License-Identifier: BSD-2-Clause
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/

#include "config.h"

#include "cache/cache_varnishd.h"

#include "vcc_h2_if.h"

#include "cache/cache_transport.h"
#include "http2/cache_http2.h"

static struct h2_sess *
h2get(VRT_CTX)
{
struct h2_sess *h2;
uintptr_t *up;

CHECK_OBJ_NOTNULL(ctx, VRT_CTX_MAGIC);
CHECK_OBJ_NOTNULL(ctx->req, REQ_MAGIC); // $Restrict client
if (ctx->req->transport != &HTTP2_transport)
return (NULL);
AZ(SES_Get_proto_priv(ctx->req->sp, &up));
CAST_OBJ_NOTNULL(h2, (void *)*up, H2_SESS_MAGIC);
return (h2);
}
VCL_BOOL
vmod_is(VRT_CTX)
{
struct h2_sess *h2 = h2get(ctx);

return (h2 != NULL);
}

#define GETSET(type, name, argname) \
type \
vmod_ ## name(VRT_CTX, struct VARGS(name) *args) \
{ \
struct h2_sess *h2 = h2get(ctx); \
type r; \
\
if (h2 == NULL) \
return (-1); \
\
if (! args->valid_ ## argname) \
return (h2->name); \
if (h2->name == args->argname) \
return (h2->name); \
\
Lck_Lock(&h2->sess->mtx); \
r = h2->name; \
if (h2->name != args->argname) { \
h2->name = args->argname; \
h2->rst_budget = h2->rapid_reset_limit; \
h2->last_rst = ctx->now; \
} \
Lck_Unlock(&h2->sess->mtx); \
return (r); \
}

GETSET(VCL_DURATION, rapid_reset, threshold)
GETSET(VCL_INT, rapid_reset_limit, number)
GETSET(VCL_DURATION, rapid_reset_period, duration)

VCL_REAL
vmod_rapid_reset_budget(VRT_CTX)
{
struct h2_sess *h2 = h2get(ctx);

if (h2 == NULL)
return (-1);

return (h2->rst_budget);
}