diff --git a/src/knot/modules/rrl/functions.c b/src/knot/modules/rrl/functions.c index fcee6c3ae5443378a4be6c4fbfe87ea68e1fdfdf..01d89cb66949eedc8486a284eb2a6c62592b1887 100644 --- a/src/knot/modules/rrl/functions.c +++ b/src/knot/modules/rrl/functions.c @@ -19,14 +19,13 @@ #include "knot/modules/rrl/functions.h" #include "knot/modules/rrl/kru.h" +#include "contrib/macros.h" #include "contrib/musl/inet_ntop.h" #include "contrib/sockaddr.h" #include "contrib/time.h" #include "libdnssec/random.h" -/* CIDR block prefix lengths for v4/v6 */ -// Hardcoded also in unit tests. - +// CIDR block prefix lengths for v4/v6 (hardcoded also in unit tests). #define RRL_V4_PREFIXES (uint8_t[]) { 18, 20, 24, 32 } #define RRL_V4_RATE_MULT (kru_price_t[]) { 768, 256, 32, 1 } @@ -41,10 +40,13 @@ #define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC #endif +#define RRL_LIMIT_KOEF 1/2 // Avoid probabilistic rounding wherever possible. + struct rrl_table { kru_price_t v4_prices[RRL_V4_PREFIXES_CNT]; kru_price_t v6_prices[RRL_V6_PREFIXES_CNT]; uint32_t log_period; + bool rw_mode; _Atomic uint32_t log_time; _Alignas(64) uint8_t kru[]; }; @@ -66,7 +68,8 @@ static void addr_tostr(char *dst, size_t maxlen, const struct sockaddr_storage * } } -static void rrl_log_limited(knotd_mod_t *mod, const struct sockaddr_storage *ss, const uint8_t prefix) +static void rrl_log_limited(knotd_mod_t *mod, const struct sockaddr_storage *ss, + const uint8_t prefix, bool rate) { if (mod == NULL) { return; @@ -75,10 +78,12 @@ static void rrl_log_limited(knotd_mod_t *mod, const struct sockaddr_storage *ss, char addr_str[SOCKADDR_STRLEN]; addr_tostr(addr_str, sizeof(addr_str), ss); - knotd_mod_log(mod, LOG_NOTICE, "address %s limited on /%d", addr_str, prefix); + knotd_mod_log(mod, LOG_NOTICE, "address %s limited on /%d by %s", + addr_str, prefix, rate ? "rate" : "time"); } -rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit, uint32_t log_period) +rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit, + bool rw_mode, uint32_t log_period) { if (size == 0 || instant_limit == 0 || rate_limit == 0) { return NULL; @@ -95,9 +100,12 @@ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit } memset(rrl, 0, rrl_size); - const kru_price_t base_price = KRU_LIMIT / instant_limit; - const kru_price_t max_decay = rate_limit > 1000ll * instant_limit ? base_price : - (uint64_t) base_price * rate_limit / 1000; + assert(rate_limit <= 1000ll * instant_limit); // Ensured by config check. + kru_price_t base_price = KRU_LIMIT / instant_limit; + const kru_price_t max_decay = (uint64_t)base_price * rate_limit / 1000; + if (!rw_mode) { + base_price = base_price * RRL_LIMIT_KOEF; + } if (!KRU.initialize((struct kru *)rrl->kru, capacity_log, max_decay)) { free(rrl); @@ -112,9 +120,10 @@ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit rrl->v6_prices[i] = base_price / RRL_V6_RATE_MULT[i]; } + rrl->rw_mode = rw_mode; rrl->log_period = log_period; - struct timespec now_ts = {0}; + struct timespec now_ts; clock_gettime(CLOCK_MONOTONIC_COARSE, &now_ts); uint32_t now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000; rrl->log_time = now - log_period; @@ -124,42 +133,104 @@ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, knotd_mod_t *mod) { - if (rrl == NULL || remote == NULL) { - return KNOT_EINVAL; - } + assert(rrl); + assert(remote); - struct timespec now_ts = {0}; + struct timespec now_ts; clock_gettime(CLOCK_MONOTONIC_COARSE, &now_ts); uint32_t now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000; - uint8_t limited_prefix; + uint16_t load = 0; + uint8_t prefix = 0; _Alignas(16) uint8_t key[16] = { 0 }; if (remote->ss_family == AF_INET6) { struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)remote; memcpy(key, &ipv6->sin6_addr, 16); - limited_prefix = KRU.limited_multi_prefix_or((struct kru *)rrl->kru, now, - 1, key, RRL_V6_PREFIXES, rrl->v6_prices, RRL_V6_PREFIXES_CNT, NULL); + if (rrl->rw_mode) { + prefix = KRU.limited_multi_prefix_or( + (struct kru *)rrl->kru, now, 1, key, RRL_V6_PREFIXES, + rrl->v6_prices, RRL_V6_PREFIXES_CNT, NULL); + } else { + load = KRU.load_multi_prefix_max( + (struct kru *)rrl->kru, now, 1, key, RRL_V6_PREFIXES, + NULL, RRL_V6_PREFIXES_CNT, &prefix); + } } else { struct sockaddr_in *ipv4 = (struct sockaddr_in *)remote; memcpy(key, &ipv4->sin_addr, 4); - limited_prefix = KRU.limited_multi_prefix_or((struct kru *)rrl->kru, now, - 0, key, RRL_V4_PREFIXES, rrl->v4_prices, RRL_V4_PREFIXES_CNT, NULL); + if (rrl->rw_mode) { + prefix = KRU.limited_multi_prefix_or( + (struct kru *)rrl->kru, now, 0, key, RRL_V4_PREFIXES, + rrl->v4_prices, RRL_V4_PREFIXES_CNT, NULL); + } else { + load = KRU.load_multi_prefix_max( + (struct kru *)rrl->kru, now, 0, key, RRL_V4_PREFIXES, + NULL, RRL_V4_PREFIXES_CNT, &prefix); + } + } + + if (rrl->rw_mode) { + if (prefix == 0) { + return KNOT_EOK; + } + } else { + if (load <= (1 << 16) * RRL_LIMIT_KOEF) { + return KNOT_EOK; + } } uint32_t log_time_orig = atomic_load_explicit(&rrl->log_time, memory_order_relaxed); - if (rrl->log_period && limited_prefix && (now - log_time_orig + 1024 >= rrl->log_period + 1024)) { + if (rrl->log_period && (now - log_time_orig + 1024 >= rrl->log_period + 1024)) { do { if (atomic_compare_exchange_weak_explicit(&rrl->log_time, &log_time_orig, now, memory_order_relaxed, memory_order_relaxed)) { - rrl_log_limited(mod, remote, limited_prefix); + rrl_log_limited(mod, remote, prefix, rrl->rw_mode); break; } } while (now - log_time_orig + 1024 >= rrl->log_period + 1024); } - return limited_prefix ? KNOT_ELIMIT : KNOT_EOK; + return KNOT_ELIMIT; +} + +void rrl_update(rrl_table_t *rrl, const struct sockaddr_storage *remote, size_t value) +{ + assert(rrl); + assert(remote); + assert(!rrl->rw_mode); + + struct timespec now_ts; + clock_gettime(CLOCK_MONOTONIC_COARSE, &now_ts); + uint32_t now = now_ts.tv_sec * 1000 + now_ts.tv_nsec / 1000000; + + _Alignas(16) uint8_t key[16] = { 0 }; + if (remote->ss_family == AF_INET6) { + struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)remote; + memcpy(key, &ipv6->sin6_addr, 16); + + kru_price_t prices[RRL_V6_PREFIXES_CNT]; + for (size_t i = 0; i < RRL_V6_PREFIXES_CNT; i++) { + prices[i] = MIN(value * rrl->v6_prices[i], (kru_price_t)-1LL); + } + + (void)KRU.load_multi_prefix_max((struct kru *)rrl->kru, now, + 1, key, RRL_V6_PREFIXES, prices, + RRL_V6_PREFIXES_CNT, NULL); + } else { + struct sockaddr_in *ipv4 = (struct sockaddr_in *)remote; + memcpy(key, &ipv4->sin_addr, 4); + + kru_price_t prices[RRL_V4_PREFIXES_CNT]; + for (size_t i = 0; i < RRL_V4_PREFIXES_CNT; i++) { + prices[i] = MIN(value * rrl->v4_prices[i], (kru_price_t)-1LL); + } + + (void)KRU.load_multi_prefix_max((struct kru *)rrl->kru, now, + 0, key, RRL_V4_PREFIXES, prices, + RRL_V4_PREFIXES_CNT, NULL); + } } bool rrl_slip_roll(int n_slip) diff --git a/src/knot/modules/rrl/functions.h b/src/knot/modules/rrl/functions.h index 934df369cc2a968493f72fa7c05447a636ea8ca8..0941c8378f057651aaff91d5ee1dbd4f5be0aefb 100644 --- a/src/knot/modules/rrl/functions.h +++ b/src/knot/modules/rrl/functions.h @@ -27,17 +27,21 @@ typedef struct rrl_table rrl_table_t; * \brief Create a RRL table. * * \param size Fixed table size. - * \param rate Rate (in pkts/sec). - * \param instant_limit Instant limit (number). + * \param instant_limit Instant limit. + * \param rate_limit Rate limit. + * \param rw_mode If disabled, RW operation is divided into R and W operations. * \param log_period If nonzero, maximum logging period (in milliseconds). * * \return created table or NULL. */ -rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit, uint32_t log_period); +rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit, + bool rw_mode, uint32_t log_period); /*! * \brief Query the RRL table for accept or deny, when the rate limit is reached. * + * \note This function is common to both RW and non-RW modes! + * * \param rrl RRL table. * \param remote Source address. * \param mod Query module (needed for logging). @@ -47,6 +51,17 @@ rrl_table_t *rrl_create(size_t size, uint32_t instant_limit, uint32_t rate_limit */ int rrl_query(rrl_table_t *rrl, const struct sockaddr_storage *remote, knotd_mod_t *mod); +/*! + * \brief Update the RRL table. + * + * \note This function is only for the non-RW mode! + * + * \param rrl RRL table. + * \param remote Source address. + * \param value Value with which the table is updated. + */ +void rrl_update(rrl_table_t *rrl, const struct sockaddr_storage *remote, size_t value); + /*! * \brief Roll a dice whether answer slips or not. * diff --git a/src/knot/modules/rrl/rrl.c b/src/knot/modules/rrl/rrl.c index 8d2ad05bef2a929eb0f5efd828ab52d72c9eddcc..e90648585124e0cb6897f7e200de4ccac9554abd 100644 --- a/src/knot/modules/rrl/rrl.c +++ b/src/knot/modules/rrl/rrl.c @@ -118,7 +118,7 @@ int rrl_load(knotd_mod_t *mod) uint32_t rate_limit = knotd_conf_mod(mod, MOD_RATE_LIMIT).single.integer; size_t size = knotd_conf_mod(mod, MOD_TBL_SIZE).single.integer; uint32_t log_period = knotd_conf_mod(mod, MOD_LOG_PERIOD).single.integer; - ctx->rrl = rrl_create(size, instant_limit, rate_limit, log_period); + ctx->rrl = rrl_create(size, instant_limit, rate_limit, true, log_period); if (ctx->rrl == NULL) { ctx_free(ctx); return KNOT_ENOMEM; diff --git a/tests/modules/test_rrl.c b/tests/modules/test_rrl.c index d948d21b4f298dfa45cf54e553d955f3345b9830..448ff9acd44236d5eb23e04df1db70323074aa66 100644 --- a/tests/modules/test_rrl.c +++ b/tests/modules/test_rrl.c @@ -32,11 +32,6 @@ int fakeclock_gettime(clockid_t clockid, struct timespec *tp); #include "knot/modules/rrl/functions.c" #undef clock_gettime -#define RRL_TABLE_SIZE (1 << 20) -#define RRL_INSTANT_LIMIT (1 << 8) -#define RRL_RATE_LIMIT (1 << 17) -#define RRL_BASE_PRICE (KRU_LIMIT / RRL_INSTANT_LIMIT) - #define RRL_THREADS 4 //#define RRL_SYNC_WITH_REAL_TIME @@ -57,14 +52,17 @@ static inline kru_price_t get_mult(uint8_t prefixes[], kru_price_t mults[], size return 0; } +// Macro correction depending on the table mode. +int DIFF = 0; + // Instant limits and rate limits per msec. -#define INST(Vx, prefix) LIMIT(INSTANT, Vx, prefix) -#define RATEM(Vx, prefix) (LIMIT(RATE, Vx, prefix) / 1000) +#define INST(Vx, prefix) (LIMIT(INSTANT, Vx, prefix) + DIFF) +#define RATEM(Vx, prefix) (LIMIT(RATE, Vx, prefix) / 1000 + DIFF) // Expected range of limits for parallel test. -#define RANGE_INST(Vx, prefix) INST(Vx, prefix) - 1, INST(Vx, prefix) + RRL_THREADS - 1 -#define RANGE_RATEM(Vx, prefix) RATEM(Vx, prefix) - 1, RATEM(Vx, prefix) -#define RANGE_UNLIM(queries) queries, queries +#define RANGE_INST(Vx, prefix) INST(Vx, prefix) - 1, INST(Vx, prefix) + RRL_THREADS - 1 +#define RANGE_RATEM(Vx, prefix) RATEM(Vx, prefix) - 1 - DIFF, RATEM(Vx, prefix) + RRL_THREADS - DIFF +#define RANGE_UNLIM(queries) queries, queries /* Fix seed for randomness in RLL module. Change if improbable collisions arise. (one byte) */ #define RRL_SEED_GENERIC 1 @@ -172,6 +170,9 @@ static void *rrl_runnable(void *arg) if (rrl_query(d->rrl, &addr, NULL) == KNOT_EOK) { atomic_fetch_add(&d->stages[si].hosts[hi].passed, 1); + if (!d->rrl->rw_mode) { + rrl_update(d->rrl, &addr, 1); + } } } while ((qi2 = (qi2 + d->prime) % (1 << BATCH_QUERIES_LOG))); @@ -200,6 +201,9 @@ void count_test(char *desc, int expected_passing, double margin_fract, cnt = i; break; } + if (!rrl->rw_mode) { + rrl_update(rrl, &addr, 1); + } } if (expected_passing < 0) expected_passing = -1; @@ -213,12 +217,20 @@ void count_test(char *desc, int expected_passing, double margin_fract, } } -void test_rrl(void) +void test_rrl(bool rw_mode) { + size_t RRL_TABLE_SIZE = (1 << 20); + uint32_t RRL_INSTANT_LIMIT = (1 << 7); + uint32_t RRL_RATE_LIMIT = (1 << 16); + if (rw_mode) { + RRL_INSTANT_LIMIT = (1 << 8); + RRL_RATE_LIMIT = (1 << 17); + } + fakeclock_init(); /* create rrl table */ - rrl = rrl_create(RRL_TABLE_SIZE, RRL_INSTANT_LIMIT, RRL_RATE_LIMIT, 0); + rrl = rrl_create(RRL_TABLE_SIZE, RRL_INSTANT_LIMIT, RRL_RATE_LIMIT, rw_mode, 0); ok(rrl != NULL, "rrl(%s): create", impl_name); assert(rrl); @@ -276,7 +288,7 @@ void test_rrl(void) count_test("IPv6 instant limit /64 not applied on /63", -1, 0, AF_INET6, "8000:0:0:1::", 0, 0); - count_test("IPv6 instant limit /56", INST(V6, 56) - INST(V6, 64) - 1, 0, + count_test("IPv6 instant limit /56", INST(V6, 56) - INST(V6, 64) - 1, rw_mode ? 0 : 0.01, AF_INET6, "8000:0:0:00%02x:%02x00::", 0x02, 0xff); count_test("IPv6 instant limit /56 not applied on /55", -1, 0, @@ -288,7 +300,7 @@ void test_rrl(void) count_test("IPv6 instant limit /48 not applied on /47", -1, 0, AF_INET6, "8000:0:1::", 0, 0); - count_test("IPv6 instant limit /32", INST(V6, 32) - INST(V6, 48) - 1, 0.001, + count_test("IPv6 instant limit /32", INST(V6, 32) - INST(V6, 48) - 1, rw_mode ? 0.001 : 0, AF_INET6, "8000:0:%02x%02x::", 0x02, 0xff); count_test("IPv6 instant limit /32 not applied on /31", -1, 0, @@ -370,24 +382,36 @@ void test_rrl(void) rrl_destroy(rrl); } -int main(int argc, char *argv[]) +void test_rrl_mode(bool test_avx2, bool rw_mode) { - plan_lazy(); - - dnssec_crypto_init(); - - assert(KRU_GENERIC.initialize != KRU_AVX2.initialize); - bool test_avx2 = (KRU.initialize == KRU_AVX2.initialize); + if (!rw_mode) { + DIFF = 1; + } KRU = KRU_GENERIC; impl_name = "KRU_GENERIC"; - test_rrl(); + test_rrl(rw_mode); if (test_avx2) { KRU = KRU_AVX2; impl_name = "KRU_AVX2"; - test_rrl(); + test_rrl(rw_mode); + } else { + diag("AVX2 NOT available"); } +} + +int main(int argc, char *argv[]) +{ + plan_lazy(); + + dnssec_crypto_init(); + + assert(KRU_GENERIC.initialize != KRU_AVX2.initialize); + bool test_avx2 = (KRU.initialize == KRU_AVX2.initialize); + + test_rrl_mode(test_avx2, true); + test_rrl_mode(test_avx2, false); dnssec_crypto_cleanup(); return 0;