From: Vladimír Čunát Date: Tue, 7 Jan 2025 09:08:07 +0000 (+0100) Subject: price_factor WIP X-Git-Tag: v6.0.10~2^2~5 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5700d965a076554f82c1265e96f46f70311287a4;p=thirdparty%2Fknot-resolver.git price_factor WIP --- diff --git a/NEWS b/NEWS index d7f2cf53c..a4e757844 100644 --- a/NEWS +++ b/NEWS @@ -10,6 +10,7 @@ Improvements - reload TLS certificate files even if the configuration has not changed (!1644) - kresctl: bash command-line TAB completion (!1622) - add request prioritization (defer) to mitigate DoS attacks (!1641) +- views: allow overriding price-factor (!1646) Knot Resolver 6.0.9 (2024-11-11) diff --git a/daemon/defer.c b/daemon/defer.c index 223a2d615..1705ede00 100644 --- a/daemon/defer.c +++ b/daemon/defer.c @@ -9,6 +9,7 @@ #include "daemon/udp_queue.h" #include "lib/kru.h" #include "lib/mmapped.h" +#include "lib/resolve.h" #include "lib/utils.h" #define V4_PREFIXES (uint8_t[]) { 18, 20, 24, 32 } @@ -234,6 +235,10 @@ void defer_str_conf(char *desc, int desc_len) #undef append } +void defer_set_price_factor16(struct kr_request *req, uint32_t price_factor16) +{ + req->qsource.price_factor16 = defer_sample_state.price_factor16 = price_factor16; +} /// Call KRU, return priority and as params load and prefix. static inline int kru_charge_classify(const struct kru_conf *kru_conf, uint8_t *key, kru_price_t *prices, @@ -260,6 +265,16 @@ static inline int kru_charge_classify(const struct kru_conf *kru_conf, uint8_t * void defer_charge(uint64_t nsec, union kr_sockaddr *addr, bool stream) { if (!stream) return; // UDP is not accounted in KRU; TODO remove !stream invocations? + + // compute time adjusted by the price factor + uint64_t nsec_adj; + const uint32_t pf16 = defer_sample_state.price_factor16; + if (pf16 == 0) return; // whitelisted + if (nsec < (1ul<<32)) { // simple way with standard rounding + nsec_adj = (nsec * pf16 + (1<<15)) >> 16; + } else { // afraid of overflow, so we swap the order of the math + nsec_adj = ((nsec + (1<<15)) >> 16) * pf16; + } _Alignas(16) uint8_t key[16] = {0, }; const struct kru_conf *kru_conf; @@ -273,7 +288,7 @@ void defer_charge(uint64_t nsec, union kr_sockaddr *addr, bool stream) return; } - uint64_t base_price = BASE_PRICE(nsec); + uint64_t base_price = BASE_PRICE(nsec_adj); kru_price_t prices[kru_conf->prefixes_cnt]; for (size_t i = 0; i < kru_conf->prefixes_cnt; i++) { uint64_t price = base_price / kru_conf->rate_mult[i]; diff --git a/daemon/defer.h b/daemon/defer.h index 18adf91b3..0b6fdf51c 100644 --- a/daemon/defer.h +++ b/daemon/defer.h @@ -20,10 +20,16 @@ void defer_deinit(void); /// Increment KRU counters by the given time. void defer_charge(uint64_t nsec, union kr_sockaddr *addr, bool stream); +struct kr_request; +/// Set the price-factor; see struct kr_request::qsource.price_factor16 +KR_EXPORT +void defer_set_price_factor16(struct kr_request *req, uint32_t price_factor16); + typedef struct { bool is_accounting; /// whether currently accounting the time to someone bool stream; union kr_sockaddr addr; /// request source (to which we account) or AF_UNSPEC if unknown yet + uint32_t price_factor16; /// see struct kr_request::qsource.price_factor16 uint64_t stamp; /// monotonic nanoseconds, probably won't wrap } defer_sample_state_t; extern defer_sample_state_t defer_sample_state; @@ -77,6 +83,7 @@ static inline void defer_sample_addr(const union kr_sockaddr *addr, bool stream) break; } defer_sample_state.stream = stream; + defer_sample_state.price_factor16 = 1 << 16; // meaning *1.0, until more information is known } /// Internal; start accounting work at specified timestamp. diff --git a/daemon/lua/kres-gen-33.lua b/daemon/lua/kres-gen-33.lua index 3fc16df3c..e8adbda63 100644 --- a/daemon/lua/kres-gen-33.lua +++ b/daemon/lua/kres-gen-33.lua @@ -233,6 +233,7 @@ struct kr_request { const knot_pkt_t *packet; struct kr_request_qsource_flags flags; struct kr_request_qsource_flags comm_flags; + uint32_t price_factor16; size_t size; int32_t stream_id; kr_http_header_array_t headers; @@ -618,6 +619,7 @@ int zi_zone_import(const zi_config_t); _Bool ratelimiting_request_begin(struct kr_request *); int ratelimiting_init(const char *, size_t, uint32_t, uint32_t, uint16_t, uint32_t, _Bool); int defer_init(const char *, uint32_t, int); +void defer_set_price_factor16(struct kr_request *, uint32_t); struct engine { char _stub[]; }; diff --git a/daemon/lua/kres-gen.sh b/daemon/lua/kres-gen.sh index a395253ba..910c5b75b 100755 --- a/daemon/lua/kres-gen.sh +++ b/daemon/lua/kres-gen.sh @@ -348,6 +348,7 @@ ${CDEFS} ${KRESD} functions <<-EOF ratelimiting_request_begin ratelimiting_init defer_init + defer_set_price_factor16 EOF echo "struct engine" | ${CDEFS} ${KRESD} types | sed '/module_array_t/,$ d' diff --git a/daemon/ratelimiting.c b/daemon/ratelimiting.c index 29d4c82a0..996beaaf6 100644 --- a/daemon/ratelimiting.c +++ b/daemon/ratelimiting.c @@ -129,6 +129,8 @@ bool ratelimiting_request_begin(struct kr_request *req) if (!ratelimiting) return false; if (!req->qsource.addr) return false; // don't consider internal requests + if (req->qsource.price_factor16 == 0) + return false; // whitelisted // We only do this on pure UDP. (also TODO if cookies get implemented) const bool ip_validated = req->qsource.flags.tcp || req->qsource.flags.tls; @@ -143,14 +145,26 @@ bool ratelimiting_request_begin(struct kr_request *req) struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)req->qsource.addr; memcpy(key, &ipv6->sin6_addr, 16); + // compute adjusted prices, using standard rounding + kru_price_t prices[V6_PREFIXES_CNT]; + for (int i = 0; i < V6_PREFIXES_CNT; ++i) { + prices[i] = (req->qsource.price_factor16 + * (uint64_t)ratelimiting->v6_prices[i] + (1<<15)) >> 16; + } limited_prefix = KRU.limited_multi_prefix_or((struct kru *)ratelimiting->kru, time_now, - 1, key, V6_PREFIXES, ratelimiting->v6_prices, V6_PREFIXES_CNT, NULL); + 1, key, V6_PREFIXES, prices, V6_PREFIXES_CNT, NULL); } else { struct sockaddr_in *ipv4 = (struct sockaddr_in *)req->qsource.addr; memcpy(key, &ipv4->sin_addr, 4); // TODO append port? + // compute adjusted prices, using standard rounding + kru_price_t prices[V4_PREFIXES_CNT]; + for (int i = 0; i < V4_PREFIXES_CNT; ++i) { + prices[i] = (req->qsource.price_factor16 + * (uint64_t)ratelimiting->v4_prices[i] + (1<<15)) >> 16; + } limited_prefix = KRU.limited_multi_prefix_or((struct kru *)ratelimiting->kru, time_now, - 0, key, V4_PREFIXES, ratelimiting->v4_prices, V4_PREFIXES_CNT, NULL); + 0, key, V4_PREFIXES, prices, V4_PREFIXES_CNT, NULL); } if (!limited_prefix) return false; // not limited diff --git a/daemon/ratelimiting.test/tests-parallel.c b/daemon/ratelimiting.test/tests-parallel.c index a32959559..44039e12e 100644 --- a/daemon/ratelimiting.test/tests-parallel.c +++ b/daemon/ratelimiting.test/tests-parallel.c @@ -62,6 +62,7 @@ static void *runnable(void *arg) knot_pkt_t answer = { .wire = wire }; struct kr_request req = { .qsource.addr = (struct sockaddr *) &addr, + .qsource.price_factor16 = 1 << 16, .answer = &answer }; diff --git a/daemon/ratelimiting.test/tests.c b/daemon/ratelimiting.test/tests.c index eb65a7249..dc1ab2b96 100644 --- a/daemon/ratelimiting.test/tests.c +++ b/daemon/ratelimiting.test/tests.c @@ -32,6 +32,7 @@ uint32_t _count_test(int expected_passing, int addr_family, char *addr_format, u knot_pkt_t answer = { .wire = wire }; struct kr_request req = { .qsource.addr = (struct sockaddr *) &addr, + .qsource.price_factor16 = 1 << 16, .answer = &answer }; char addr_str[40]; diff --git a/daemon/worker.c b/daemon/worker.c index ece3fd953..07480b24c 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -133,6 +133,15 @@ static void subreq_finalize(struct qr_task *task, const struct sockaddr *packet_ struct worker_ctx the_worker_value; /**< Static allocation is suitable for the singleton. */ struct worker_ctx *the_worker = NULL; + +static inline void defer_sample_task(const struct qr_task *task) +{ + if (task && task->ctx->source.session) { + defer_sample_addr(&task->ctx->source.addr, task->ctx->source.session->stream); + defer_sample_state.price_factor16 = task->ctx->req.qsource.price_factor16; + } +} + /*! @internal Create a UDP/TCP handle for an outgoing AF_INET* connection. * socktype is SOCK_* */ static struct session2 *ioreq_spawn(int socktype, sa_family_t family, @@ -335,6 +344,7 @@ static struct request_ctx *request_create(struct session2 *session, req->vars_ref = LUA_NOREF; req->uid = uid; req->qsource.comm_flags.xdp = comm && comm->xdp; + req->qsource.price_factor16 = 1 << 16; // meaning *1.0 kr_request_set_extended_error(req, KNOT_EDNS_EDE_NONE, NULL); array_init(req->qsource.headers); if (session) { @@ -711,8 +721,7 @@ static int send_waiting(struct session2 *session) int ret = 0; do { struct qr_task *t = session2_waitinglist_get(session); - if (t->ctx->source.session) - defer_sample_addr(&t->ctx->source.addr, t->ctx->source.session->stream); + defer_sample_task(t); ret = qr_task_send(t, session, NULL, NULL); defer_sample_restart(); if (ret != 0) { @@ -966,8 +975,7 @@ static int qr_task_finalize(struct qr_task *task, int state) return kr_ok(); } - if (task->ctx->source.session) - defer_sample_addr(&task->ctx->source.addr, task->ctx->source.session->stream); + defer_sample_task(task); struct request_ctx *ctx = task->ctx; struct session2 *source_session = ctx->source.session; @@ -1251,8 +1259,7 @@ static int tcp_task_step(struct qr_task *task, static int qr_task_step(struct qr_task *task, const struct sockaddr *packet_source, knot_pkt_t *packet) { - if (task && task->ctx->source.session) - defer_sample_addr(&task->ctx->source.addr, task->ctx->source.session->stream); + defer_sample_task(task); /* No more steps after we're finished. */ if (!task || task->finished) { diff --git a/lib/resolve.h b/lib/resolve.h index cbc20877e..b3e6614d2 100644 --- a/lib/resolve.h +++ b/lib/resolve.h @@ -245,6 +245,10 @@ struct kr_request { * this describes the request from the proxy. When there is no * proxy, this will have exactly the same value as `flags`. */ struct kr_request_qsource_flags comm_flags; + /** Option price-factor from views: + * - represented as 16+16-bit fixed-point number + * - affects ratelimiting and defer */ + uint32_t price_factor16; size_t size; /**< query packet size */ int32_t stream_id; /**< HTTP/2 stream ID for DoH requests */ kr_http_header_array_t headers; /**< HTTP/2 headers for DoH requests */ diff --git a/python/knot_resolver/datamodel/templates/views.lua.j2 b/python/knot_resolver/datamodel/templates/views.lua.j2 index 81de8c7b7..0b6022391 100644 --- a/python/knot_resolver/datamodel/templates/views.lua.j2 +++ b/python/knot_resolver/datamodel/templates/views.lua.j2 @@ -12,7 +12,9 @@ assert(C.kr_view_insert_action('{{ subnet }}', '{{ view.dst_subnet or '' }}', {% if flags %} {{ quotes(policy_flags(flags)) }}, {%- endif %} - +{% if view.options.price_factor|float != 1.0 %} + 'C.defer_set_price_factor16(req, {{ (view.options.price_factor|float * 2**16)|round|int }})', +{%- endif %} {% if view.tags %} {{ policy_tags_assign(view.tags) }}, {% elif view.answer %} diff --git a/python/knot_resolver/datamodel/view_schema.py b/python/knot_resolver/datamodel/view_schema.py index be678e683..34305c753 100644 --- a/python/knot_resolver/datamodel/view_schema.py +++ b/python/knot_resolver/datamodel/view_schema.py @@ -1,6 +1,7 @@ from typing import List, Literal, Optional from knot_resolver.datamodel.types import IDPattern, IPNetwork +from knot_resolver.datamodel.types import FloatNonNegative from knot_resolver.utils.modeling import ConfigSchema @@ -15,6 +16,7 @@ class ViewOptionsSchema(ConfigSchema): minimize: bool = True dns64: bool = True + price_factor: FloatNonNegative = FloatNonNegative(1.0) class ViewSchema(ConfigSchema):