From: Lukáš Ondráček Date: Wed, 27 Mar 2024 10:45:30 +0000 (+0100) Subject: rrl: configurable limits in yaml, deinit X-Git-Tag: v6.0.9~1^2~70 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=refs%2Fenvironments%2Fdocs-develop-rrl-8r8r8r%2Fdeployments%2F3624;p=thirdparty%2Fknot-resolver.git rrl: configurable limits in yaml, deinit --- diff --git a/daemon/lua/kres-gen-30.lua b/daemon/lua/kres-gen-30.lua index 65c6ac5ac..937453e92 100644 --- a/daemon/lua/kres-gen-30.lua +++ b/daemon/lua/kres-gen-30.lua @@ -580,6 +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); struct engine { char _stub[]; }; diff --git a/daemon/lua/kres-gen-31.lua b/daemon/lua/kres-gen-31.lua index cb96adf12..a8e179383 100644 --- a/daemon/lua/kres-gen-31.lua +++ b/daemon/lua/kres-gen-31.lua @@ -580,6 +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); struct engine { char _stub[]; }; diff --git a/daemon/lua/kres-gen-32.lua b/daemon/lua/kres-gen-32.lua index 7a416079c..54be1b0bb 100644 --- a/daemon/lua/kres-gen-32.lua +++ b/daemon/lua/kres-gen-32.lua @@ -581,6 +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); struct engine { char _stub[]; }; diff --git a/daemon/lua/kres-gen.sh b/daemon/lua/kres-gen.sh index 752944979..8860ef401 100755 --- a/daemon/lua/kres-gen.sh +++ b/daemon/lua/kres-gen.sh @@ -335,6 +335,7 @@ ${CDEFS} ${KRESD} functions <<-EOF worker_resolve_start zi_zone_import kr_rrl_request_begin + kr_rrl_initialize EOF echo "struct engine" | ${CDEFS} ${KRESD} types | sed '/module_array_t/,$ d' diff --git a/daemon/main.c b/daemon/main.c index 53ecb3e8f..1f65ea77a 100644 --- a/daemon/main.c +++ b/daemon/main.c @@ -10,6 +10,7 @@ #include "daemon/network.h" #include "daemon/udp_queue.h" #include "daemon/worker.h" +#include "daemon/rrl/api.h" #ifdef ENABLE_DOH2 #include "daemon/http.h" @@ -647,6 +648,7 @@ int main(int argc, char **argv) cleanup:/* Cleanup. */ network_unregister(); + kr_rrl_deinit(); kr_resolver_deinit(); worker_deinit(); engine_deinit(); diff --git a/daemon/rrl/api.c b/daemon/rrl/api.c index 19c2f1779..d6b332933 100644 --- a/daemon/rrl/api.c +++ b/daemon/rrl/api.c @@ -26,11 +26,17 @@ struct rrl { uint8_t kru[] ALIGNED(64); }; struct rrl *the_rrl = NULL; +int the_rrl_fd = -1; +char *the_rrl_mmap_file = NULL; -void kr_rrl_init(char *mmap_path, size_t capacity, uint32_t instant_limit, uint32_t rate_limit) { - int fd = open(mmap_path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); +void kr_rrl_init(const char *mmap_file, size_t capacity, uint32_t instant_limit, uint32_t rate_limit) +{ + int fd = the_rrl_fd = open(mmap_file, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); kr_require(fd != -1); + the_rrl_mmap_file = malloc(strlen(mmap_file) + 1); + strcpy(the_rrl_mmap_file, mmap_file); + size_t capacity_log = 0; for (size_t c = capacity - 1; c > 0; c >>= 1) capacity_log++; @@ -99,11 +105,37 @@ void kr_rrl_init(char *mmap_path, size_t capacity, uint32_t instant_limit, uint3 kr_require(false); // we can get here for example if signal interrupt is received during fcntl } -bool kr_rrl_request_begin(struct kr_request *req) +void kr_rrl_deinit(void) { - if (!the_rrl) { - kr_rrl_init("/tmp/kresd-kru", 524288, 20, 5); // TODO call from elsewhere + if (the_rrl == NULL) return; + int fd = the_rrl_fd; + + struct flock fl = { + .l_type = F_UNLCK, + .l_whence = SEEK_SET, + .l_start = 0, + .l_len = 0 }; + fcntl(fd, F_SETLK, &fl); // unlock + + fl.l_type = F_WRLCK; + if (fcntl(fd, F_SETLK, &fl) != -1) { + + /* If the RRL configuration is updated at runtime, manager should remove the file (TODO) + * and keep new processes create it again while old processes are still using the old data. + * Here we keep zero-size file not to accidentally remove the new file instead of the old one. + * Still truncating the file will cause currently starting processes waiting for read lock on the same file to fail, + * but this is not expected to happen. */ + ftruncate(fd, 0); + + fl.l_type = F_UNLCK; + fcntl(fd, F_SETLK, &fl); } + + the_rrl = NULL; +} + +bool kr_rrl_request_begin(struct kr_request *req) +{ if (!req->qsource.addr) return false; // don't consider internal requests bool limited = false; diff --git a/daemon/rrl/api.h b/daemon/rrl/api.h index 0d155d099..8553c4bdc 100644 --- a/daemon/rrl/api.h +++ b/daemon/rrl/api.h @@ -3,6 +3,16 @@ #include struct kr_request; +/** Initialize rate-limiting with shared mmapped memory. + * 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); + /** Do rate-limiting, during knot_layer_api::begin. */ KR_EXPORT bool kr_rrl_request_begin(struct kr_request *req); + +/** Remove mmapped file data if not used by other processes. */ +KR_EXPORT +void kr_rrl_deinit(void); diff --git a/manager/knot_resolver_manager/datamodel/config_schema.py b/manager/knot_resolver_manager/datamodel/config_schema.py index 8aa99446c..9da7007eb 100644 --- a/manager/knot_resolver_manager/datamodel/config_schema.py +++ b/manager/knot_resolver_manager/datamodel/config_schema.py @@ -21,6 +21,7 @@ from knot_resolver_manager.datamodel.templates import MAIN_TEMPLATE from knot_resolver_manager.datamodel.types import Dir, EscapedStr, IntPositive from knot_resolver_manager.datamodel.view_schema import ViewSchema from knot_resolver_manager.datamodel.webmgmt_schema import WebmgmtSchema +from knot_resolver_manager.datamodel.rate_limiting_schema import RateLimitingSchema from knot_resolver_manager.utils.modeling import ConfigSchema from knot_resolver_manager.utils.modeling.base_schema import lazy_default @@ -74,6 +75,7 @@ class KresConfig(ConfigSchema): logging: Logging and debugging configuration. monitoring: Metrics exposisition configuration (Prometheus, Graphite) lua: Custom Lua configuration. + rate_limiting: ... TODO """ version: int = 1 @@ -94,6 +96,7 @@ class KresConfig(ConfigSchema): dns64: Union[bool, Dns64Schema] = False logging: LoggingSchema = LoggingSchema() monitoring: MonitoringSchema = MonitoringSchema() + rate_limiting: Optional[RateLimitingSchema] = None lua: LuaSchema = LuaSchema() _LAYER = Raw @@ -115,6 +118,7 @@ class KresConfig(ConfigSchema): dns64: Union[Literal[False], Dns64Schema] logging: LoggingSchema monitoring: MonitoringSchema + rate_limiting: Optional[RateLimitingSchema] lua: LuaSchema def _hostname(self, obj: Raw) -> Any: diff --git a/manager/knot_resolver_manager/datamodel/rate_limiting_schema.py b/manager/knot_resolver_manager/datamodel/rate_limiting_schema.py new file mode 100644 index 000000000..c38727ff0 --- /dev/null +++ b/manager/knot_resolver_manager/datamodel/rate_limiting_schema.py @@ -0,0 +1,26 @@ +#from typing_extensions import Literal + +from knot_resolver_manager.utils.modeling import ConfigSchema + +class RateLimitingSchema(ConfigSchema): + """ + Configuration of rate limiting. + + --- + 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. + """ + + capacity: int = 524288 + rate_limit: int + instant_limit: int = 50 + + def _validate(self) -> None: + max_instant_limit = int(2 ** 32 / 768 - 1); + if not 1 <= self.instant_limit <= max_instant_limit: + raise ValueError(f"'instant-limit' should be in range 1..{max_instant_limit}") + if not 1 <= self.rate_limit <= 1000 * self.instant_limit: + raise ValueError(f"'rate-limit' should be in range 1..(1000 * instant-limit)") + if not 0 < self.capacity: + raise ValueError(f"'capacity' should be positive") diff --git a/manager/knot_resolver_manager/datamodel/templates/config.lua.j2 b/manager/knot_resolver_manager/datamodel/templates/config.lua.j2 index 856dcf399..fc8239361 100644 --- a/manager/knot_resolver_manager/datamodel/templates/config.lua.j2 +++ b/manager/knot_resolver_manager/datamodel/templates/config.lua.j2 @@ -46,6 +46,9 @@ nsid.name('{{ cfg.nsid }}' .. worker.id) -- DNS64 section ------------------------------------ {% include "dns64.lua.j2" %} +-- RATE-LIMITING section ------------------------------------ +{% include "rate_limiting.lua.j2" %} + {% endif %} -- LUA section -------------------------------------- diff --git a/manager/knot_resolver_manager/datamodel/templates/rate_limiting.lua.j2 b/manager/knot_resolver_manager/datamodel/templates/rate_limiting.lua.j2 new file mode 100644 index 000000000..6622d1d14 --- /dev/null +++ b/manager/knot_resolver_manager/datamodel/templates/rate_limiting.lua.j2 @@ -0,0 +1,12 @@ +{% from 'macros/common_macros.lua.j2' import boolean %} + +modules = { 'hints > iterate' } + +{% if cfg.rate_limiting.rate_limit -%} +local ffi = require('ffi') +ffi.C.kr_rrl_init( + '{{ cfg.rundir }}/rrl', + {{ cfg.rate_limiting.capacity }}, + {{ cfg.rate_limiting.instant_limit }}, + {{ cfg.rate_limiting.rate_limit }}) +{%- endif %}