]> git.ipfire.org Git - thirdparty/knot-resolver.git/commitdiff
price_factor WIP
authorVladimír Čunát <vladimir.cunat@nic.cz>
Tue, 7 Jan 2025 09:08:07 +0000 (10:08 +0100)
committerVladimír Čunát <vladimir.cunat@nic.cz>
Sun, 19 Jan 2025 18:40:58 +0000 (19:40 +0100)
12 files changed:
NEWS
daemon/defer.c
daemon/defer.h
daemon/lua/kres-gen-33.lua
daemon/lua/kres-gen.sh
daemon/ratelimiting.c
daemon/ratelimiting.test/tests-parallel.c
daemon/ratelimiting.test/tests.c
daemon/worker.c
lib/resolve.h
python/knot_resolver/datamodel/templates/views.lua.j2
python/knot_resolver/datamodel/view_schema.py

diff --git a/NEWS b/NEWS
index d7f2cf53cedc5bde0684dfdde785133615fa4b3f..a4e757844c07f82ce89b385c633d24ad2384fe0f 100644 (file)
--- 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)
index 223a2d615e4bfff8fee521e9cb5ecd0d56f29de1..1705ede00fe316458dadfc04f3daa9fac919754e 100644 (file)
@@ -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];
index 18adf91b3ce12b66136251b30e20a3c93a5b5a70..0b6fdf51c02ed4d2e9150d4be6c79cf54668b771 100644 (file)
@@ -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.
index 3fc16df3cd7d3ffd1b786b234c9a5150f1c85707..e8adbda63e02d658270fdd360eb7ee8ca4840d4f 100644 (file)
@@ -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[];
 };
index a395253ba02295fb5114d38e017ac7d450fd0914..910c5b75bb420477fe64cbf8c90534e35f1ee9ca 100755 (executable)
@@ -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'
index 29d4c82a0ebba8beabac6ad58429eb93f18ae9ce..996beaaf6170f733f8d9e3297ce829a5c6a39602 100644 (file)
@@ -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
 
index a329595595aeafbb20d265df22a7eb2fb2253cd5..44039e12ef36c0ef474639947ba33951c72a4494 100644 (file)
@@ -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
        };
 
index eb65a72491346af48383120cc85bfc1680f580ef..dc1ab2b960d84cd6980bb3382e49102f08278d48 100644 (file)
@@ -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];
index ece3fd95396a06f7a9bd5dc3c9f717b37f287711..07480b24ca6f5e133243971bcada295b8351fe6e 100644 (file)
@@ -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) {
index cbc20877e85a21d9076a133658df33876fc3f223..b3e6614d2bf6dc0669edefe5a24f0503dfc39355 100644 (file)
@@ -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 */
index 81de8c7b78d28ef04ad0eb856d2094046c71ce37..0b60223917ca7c5fadb42c11456eaf4f32e533b4 100644 (file)
@@ -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 %}
index be678e683bb85653e84b7207117ce8842e92e3e5..34305c7534d94019e5c7f347810650c2c2bc3d26 100644 (file)
@@ -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):