- 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)
#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 }
#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,
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;
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];
/// 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;
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.
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;
_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[];
};
ratelimiting_request_begin
ratelimiting_init
defer_init
+ defer_set_price_factor16
EOF
echo "struct engine" | ${CDEFS} ${KRESD} types | sed '/module_array_t/,$ d'
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;
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
knot_pkt_t answer = { .wire = wire };
struct kr_request req = {
.qsource.addr = (struct sockaddr *) &addr,
+ .qsource.price_factor16 = 1 << 16,
.answer = &answer
};
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];
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,
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) {
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) {
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;
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) {
* 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 */
{% 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 %}
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
minimize: bool = True
dns64: bool = True
+ price_factor: FloatNonNegative = FloatNonNegative(1.0)
class ViewSchema(ConfigSchema):