From: Lukáš Ondráček Date: Mon, 6 May 2024 15:32:45 +0000 (+0200) Subject: rrl: truncating answers when close to limit, dropping over limit X-Git-Tag: v6.0.9~1^2~54 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=9835bd32b3ee1dd24aa3711f25e29eb846163ddc;p=thirdparty%2Fknot-resolver.git rrl: truncating answers when close to limit, dropping over limit --- diff --git a/daemon/lua/kres-gen-30.lua b/daemon/lua/kres-gen-30.lua index 937453e92..9401279e8 100644 --- a/daemon/lua/kres-gen-30.lua +++ b/daemon/lua/kres-gen-30.lua @@ -580,7 +580,7 @@ knot_pkt_t *worker_resolve_mk_pkt(const char *, uint16_t, uint16_t, const struct struct qr_task *worker_resolve_start(knot_pkt_t *, struct kr_qflags); int zi_zone_import(const zi_config_t); _Bool kr_rrl_request_begin(struct kr_request *); -void kr_rrl_init(const char *, size_t, uint32_t, uint32_t); +void kr_rrl_init(const char *, size_t, uint32_t, uint32_t, int); struct engine { char _stub[]; }; diff --git a/daemon/lua/kres-gen-31.lua b/daemon/lua/kres-gen-31.lua index a8e179383..a8b14626c 100644 --- a/daemon/lua/kres-gen-31.lua +++ b/daemon/lua/kres-gen-31.lua @@ -580,7 +580,7 @@ knot_pkt_t *worker_resolve_mk_pkt(const char *, uint16_t, uint16_t, const struct struct qr_task *worker_resolve_start(knot_pkt_t *, struct kr_qflags); int zi_zone_import(const zi_config_t); _Bool kr_rrl_request_begin(struct kr_request *); -void kr_rrl_init(const char *, size_t, uint32_t, uint32_t); +void kr_rrl_init(const char *, size_t, uint32_t, uint32_t, int); struct engine { char _stub[]; }; diff --git a/daemon/lua/kres-gen-32.lua b/daemon/lua/kres-gen-32.lua index 54be1b0bb..7cbf817a3 100644 --- a/daemon/lua/kres-gen-32.lua +++ b/daemon/lua/kres-gen-32.lua @@ -581,7 +581,7 @@ knot_pkt_t *worker_resolve_mk_pkt(const char *, uint16_t, uint16_t, const struct struct qr_task *worker_resolve_start(knot_pkt_t *, struct kr_qflags); int zi_zone_import(const zi_config_t); _Bool kr_rrl_request_begin(struct kr_request *); -void kr_rrl_init(const char *, size_t, uint32_t, uint32_t); +void kr_rrl_init(const char *, size_t, uint32_t, uint32_t, int); struct engine { char _stub[]; }; diff --git a/daemon/rrl/api.c b/daemon/rrl/api.c index a33966615..5aa729e61 100644 --- a/daemon/rrl/api.c +++ b/daemon/rrl/api.c @@ -21,6 +21,7 @@ struct rrl { size_t capacity; uint32_t instant_limit; uint32_t rate_limit; + uint16_t tc_limit; bool using_avx2; kru_price_t v4_prices[RRL_V4_PREFIXES_CNT]; kru_price_t v6_prices[RRL_V6_PREFIXES_CNT]; @@ -38,7 +39,7 @@ static bool using_avx2(void) return result; } -void kr_rrl_init(const char *mmap_file, size_t capacity, uint32_t instant_limit, uint32_t rate_limit) +void kr_rrl_init(const char *mmap_file, size_t capacity, uint32_t instant_limit, uint32_t rate_limit, int tc_limit_perc) { int fd = the_rrl_fd = open(mmap_file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); if (fd == -1) { @@ -55,6 +56,8 @@ void kr_rrl_init(const char *mmap_file, size_t capacity, uint32_t instant_limit, size_t size = offsetof(struct rrl, kru) + KRU.get_size(capacity_log); + uint16_t tc_limit = (tc_limit_perc == 100 ? -1 : ((uint32_t)tc_limit_perc << 16) / 100); + // try to acquire write lock; initialize KRU on success struct flock fl = { .l_type = F_WRLCK, @@ -74,6 +77,7 @@ void kr_rrl_init(const char *mmap_file, size_t capacity, uint32_t instant_limit, the_rrl->capacity = capacity; the_rrl->instant_limit = instant_limit; the_rrl->rate_limit = rate_limit; + the_rrl->tc_limit = tc_limit; the_rrl->using_avx2 = using_avx2(); const kru_price_t base_price = KRU_LIMIT / instant_limit; @@ -110,7 +114,7 @@ void kr_rrl_init(const char *mmap_file, size_t capacity, uint32_t instant_limit, the_rrl = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); kr_require(the_rrl != MAP_FAILED); if ((the_rrl->capacity != capacity) || (the_rrl->instant_limit != instant_limit) || - (the_rrl->rate_limit != rate_limit)) goto check_fail; + (the_rrl->rate_limit != rate_limit) || (the_rrl->tc_limit != tc_limit)) goto check_fail; if (using_avx2() != the_rrl->using_avx2) goto check_fail; kr_log_info(SYSTEM, "Using existing RRL data.\n"); @@ -157,40 +161,52 @@ bool kr_rrl_request_begin(struct kr_request *req) { if (!req->qsource.addr) return false; // don't consider internal requests - bool limited = false; + uint8_t limited = 0; // 0: not limited, 1: truncated, 2: no answer + uint16_t max_final_load = 0; if (the_rrl) { uint8_t key[16] ALIGNED(16) = {0, }; uint8_t limited_prefix; - // uint16_t max_final_load = 0; // TODO use for query ordering and/or soft limit with TC=1 if (req->qsource.addr->sa_family == AF_INET6) { struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)req->qsource.addr; memcpy(key, &ipv6->sin6_addr, 16); limited_prefix = KRU.limited_multi_prefix_or((struct kru *)the_rrl->kru, kr_now(), - 1, key, RRL_V6_PREFIXES, the_rrl->v6_prices, RRL_V6_PREFIXES_CNT, /* &max_final_load */ NULL); + 1, key, RRL_V6_PREFIXES, the_rrl->v6_prices, RRL_V6_PREFIXES_CNT, &max_final_load); } else { struct sockaddr_in *ipv4 = (struct sockaddr_in *)req->qsource.addr; memcpy(key, &ipv4->sin_addr, 4); // TODO append port? limited_prefix = KRU.limited_multi_prefix_or((struct kru *)the_rrl->kru, kr_now(), - 0, key, RRL_V4_PREFIXES, the_rrl->v4_prices, RRL_V4_PREFIXES_CNT, /* &max_final_load */ NULL); + 0, key, RRL_V4_PREFIXES, the_rrl->v4_prices, RRL_V4_PREFIXES_CNT, &max_final_load); } - limited = limited_prefix; + limited = (limited_prefix ? 2 : (max_final_load > the_rrl->tc_limit ? 1 : 0)); } - if (!limited) return limited; + if (!limited) return false; knot_pkt_t *answer = kr_request_ensure_answer(req); if (!answer) { // something bad; TODO: perhaps improve recovery from this kr_assert(false); - return limited; + return true; } // at this point the packet should be pretty clear - // Example limiting: REFUSED. - knot_wire_set_rcode(answer->wire, KNOT_RCODE_REFUSED); - kr_request_set_extended_error(req, KNOT_EDNS_EDE_OTHER, "YRAA: rate-limited"); - - req->state = KR_STATE_DONE; + if (limited == 1) { + // TC=1. + knot_wire_set_tc(answer->wire); + knot_wire_clear_ad(answer->wire); + req->state = KR_STATE_DONE; + } else { + /* + // Example limiting: REFUSED. + knot_wire_set_rcode(answer->wire, KNOT_RCODE_REFUSED); + kr_request_set_extended_error(req, KNOT_EDNS_EDE_OTHER, "YRAA: rate-limited"); + req->state = KR_STATE_DONE; + */ + + // no answer + req->options.NO_ANSWER = true; + req->state = KR_STATE_FAIL; + } - return limited; + return true; } diff --git a/daemon/rrl/api.h b/daemon/rrl/api.h index 8553c4bdc..d6f5841ed 100644 --- a/daemon/rrl/api.h +++ b/daemon/rrl/api.h @@ -7,7 +7,7 @@ struct kr_request; * The existing data are used if another instance is already using the file * and it was initialized with the same parameters; it fails on mismatch. */ KR_EXPORT -void kr_rrl_init(const char *mmap_file, size_t capacity, uint32_t instant_limit, uint32_t rate_limit); +void kr_rrl_init(const char *mmap_file, size_t capacity, uint32_t instant_limit, uint32_t rate_limit, int tc_limit_perc); /** Do rate-limiting, during knot_layer_api::begin. */ KR_EXPORT diff --git a/daemon/rrl/tests.inc.c b/daemon/rrl/tests.inc.c index ec21b15b0..a352d94c0 100644 --- a/daemon/rrl/tests.inc.c +++ b/daemon/rrl/tests.inc.c @@ -95,7 +95,7 @@ static void test_rrl(void **state) { const char *tmpdir = test_tmpdir_create(); char mmap_file[64]; stpcpy(stpcpy(mmap_file, tmpdir), "/rrl"); - kr_rrl_init(mmap_file, RRL_TABLE_SIZE, RRL_INSTANT_LIMIT, RRL_RATE_LIMIT); + kr_rrl_init(mmap_file, RRL_TABLE_SIZE, RRL_INSTANT_LIMIT, RRL_RATE_LIMIT, 100); if (KRU.initialize == KRU_GENERIC.initialize) { struct kru_generic *kru = (struct kru_generic *) the_rrl->kru; diff --git a/manager/knot_resolver_manager/datamodel/rate_limiting_schema.py b/manager/knot_resolver_manager/datamodel/rate_limiting_schema.py index 20706aa50..f2b99f441 100644 --- a/manager/knot_resolver_manager/datamodel/rate_limiting_schema.py +++ b/manager/knot_resolver_manager/datamodel/rate_limiting_schema.py @@ -1,4 +1,5 @@ from knot_resolver_manager.utils.modeling import ConfigSchema +from knot_resolver_manager.datamodel.types import Percent class RateLimitingSchema(ConfigSchema): @@ -9,11 +10,13 @@ class RateLimitingSchema(ConfigSchema): capacity: Expected maximal number of blocked networks/hosts at the same time. rate_limit: Number of allowed queries per second from a single host. instant_limit: Number of allowed queries at a single point in time from a single host. + tc_limit_perc: Percent of rate/instant limit from which responses are sent as truncated. """ capacity: int = 524288 rate_limit: int instant_limit: int = 50 + tc_limit_perc: Percent = Percent(90); def _validate(self) -> None: max_instant_limit = int(2**32 / 768 - 1) diff --git a/manager/knot_resolver_manager/datamodel/templates/rate_limiting.lua.j2 b/manager/knot_resolver_manager/datamodel/templates/rate_limiting.lua.j2 index cb83045fa..552a9dcb2 100644 --- a/manager/knot_resolver_manager/datamodel/templates/rate_limiting.lua.j2 +++ b/manager/knot_resolver_manager/datamodel/templates/rate_limiting.lua.j2 @@ -5,5 +5,6 @@ C.kr_rrl_init( '{{ cfg.rundir }}/rrl', {{ cfg.rate_limiting.capacity }}, {{ cfg.rate_limiting.instant_limit }}, - {{ cfg.rate_limiting.rate_limit }}) + {{ cfg.rate_limiting.rate_limit }}, + {{ cfg.rate_limiting.tc_limit_perc }}) {%- endif %}