Skip to content

Commit 1d8356f

Browse files
author
Hynek Šabacký
committed
dnamelimiting: Add technique for limiting based on domain name lengths (without functional tests)
1 parent c0cde03 commit 1d8356f

File tree

5 files changed

+418
-1
lines changed

5 files changed

+418
-1
lines changed

daemon/dnamelimiting.c

+191
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
/* Copyright (C) CZ.NIC, z.s.p.o. <[email protected]>
2+
* SPDX-License-Identifier: GPL-3.0-or-later
3+
*/
4+
5+
#include <stdatomic.h>
6+
#include "daemon/dnamelimiting.h"
7+
#include "lib/mmapped.h"
8+
#include "lib/utils.h"
9+
#include "lib/resolve.h"
10+
11+
#define V4_PREFIXES (uint8_t[]) { 18, 20, 24, 32 }
12+
#define V4_RATE_MULT (kru_price_t[]) { 768, 256, 32, 1 }
13+
14+
#define V6_PREFIXES (uint8_t[]) { 32, 48, 56, 64, 128 }
15+
#define V6_RATE_MULT (kru_price_t[]) { 64, 4, 3, 2, 1 }
16+
17+
#define V4_PREFIXES_CNT (sizeof(V4_PREFIXES) / sizeof(*V4_PREFIXES))
18+
#define V6_PREFIXES_CNT (sizeof(V6_PREFIXES) / sizeof(*V6_PREFIXES))
19+
#define MAX_PREFIXES_CNT ((V4_PREFIXES_CNT > V6_PREFIXES_CNT) ? V4_PREFIXES_CNT : V6_PREFIXES_CNT)
20+
21+
struct dnamelimiting {
22+
size_t capacity;
23+
uint32_t instant_limit;
24+
uint32_t rate_limit;
25+
uint32_t log_period;
26+
uint16_t slip;
27+
bool dry_run;
28+
bool using_avx2;
29+
_Atomic uint32_t log_time;
30+
kru_price_t v4_prices[V4_PREFIXES_CNT];
31+
kru_price_t v6_prices[V6_PREFIXES_CNT];
32+
_Alignas(64) uint8_t kru[];
33+
};
34+
struct dnamelimiting *dnamelimiting = NULL;
35+
struct mmapped dnamelimiting_mmapped = {0};
36+
37+
/// return whether we're using optimized variant right now
38+
static bool using_avx2(void)
39+
{
40+
bool result = (KRU.initialize == KRU_AVX2.initialize);
41+
kr_require(result || KRU.initialize == KRU_GENERIC.initialize);
42+
return result;
43+
}
44+
45+
int dnamelimiting_init(const char *mmap_file, size_t capacity, uint32_t instant_limit,
46+
uint32_t rate_limit, uint16_t slip, uint32_t log_period, bool dry_run)
47+
{
48+
49+
size_t capacity_log = 0;
50+
for (size_t c = capacity - 1; c > 0; c >>= 1) capacity_log++;
51+
52+
size_t size = offsetof(struct dnamelimiting, kru) + KRU.get_size(capacity_log);
53+
54+
struct dnamelimiting header = {
55+
.capacity = capacity,
56+
.instant_limit = instant_limit,
57+
.rate_limit = rate_limit,
58+
.log_period = log_period,
59+
.slip = slip,
60+
.dry_run = dry_run,
61+
.using_avx2 = using_avx2()
62+
};
63+
64+
size_t header_size = offsetof(struct dnamelimiting, using_avx2) + sizeof(header.using_avx2);
65+
static_assert( // no padding up to .using_avx2
66+
offsetof(struct dnamelimiting, using_avx2) ==
67+
sizeof(header.capacity) +
68+
sizeof(header.instant_limit) +
69+
sizeof(header.rate_limit) +
70+
sizeof(header.log_period) +
71+
sizeof(header.slip) +
72+
sizeof(header.dry_run),
73+
"detected padding with undefined data inside mmapped header");
74+
75+
int ret = mmapped_init(&dnamelimiting_mmapped, mmap_file, size, &header, header_size);
76+
if (ret == MMAPPED_WAS_FIRST) {
77+
kr_log_info(SYSTEM, "Initializing rate-limiting...\n");
78+
79+
dnamelimiting = dnamelimiting_mmapped.mem;
80+
81+
const kru_price_t base_price = KRU_LIMIT / instant_limit;
82+
const kru_price_t max_decay = rate_limit > 1000ll * instant_limit ? base_price :
83+
(uint64_t) base_price * rate_limit / 1000;
84+
85+
printf("base price: %d\n", base_price);
86+
87+
bool succ = KRU.initialize((struct kru *)dnamelimiting->kru, capacity_log, max_decay);
88+
if (!succ) {
89+
dnamelimiting = NULL;
90+
ret = kr_error(EINVAL);
91+
goto fail;
92+
}
93+
94+
dnamelimiting->log_time = kr_now() - log_period;
95+
96+
ret = mmapped_init_continue(&dnamelimiting_mmapped);
97+
if (ret != 0) goto fail;
98+
99+
kr_log_info(SYSTEM, "Rate-limiting initialized (%s).\n", (dnamelimiting->using_avx2 ? "AVX2" : "generic"));
100+
return 0;
101+
} else if (ret == 0) {
102+
dnamelimiting = dnamelimiting_mmapped.mem;
103+
kr_log_info(SYSTEM, "Using existing rate-limiting data (%s).\n", (dnamelimiting->using_avx2 ? "AVX2" : "generic"));
104+
return 0;
105+
} // else fail
106+
107+
fail:
108+
109+
kr_log_crit(SYSTEM, "Initialization of shared rate-limiting data failed.\n");
110+
return ret;
111+
}
112+
113+
void dnamelimiting_deinit(void)
114+
{
115+
mmapped_deinit(&dnamelimiting_mmapped);
116+
dnamelimiting = NULL;
117+
}
118+
119+
120+
121+
122+
bool dnamelimiting_request_begin(struct kr_request *req, kru_price_t instant_limit)
123+
{
124+
if (!dnamelimiting) return false;
125+
if (!req->qsource.addr)
126+
return false; // don't consider internal requests
127+
if (req->qsource.price_factor16 == 0)
128+
return false; // whitelisted
129+
130+
// We only do this on pure UDP. (also TODO if cookies get implemented)
131+
const bool ip_validated = req->qsource.flags.tcp || req->qsource.flags.tls;
132+
if (ip_validated) return false;
133+
134+
uint8_t dname_size;
135+
if (req->current_query) {
136+
if (req->current_query->sname) {
137+
dname_size = strlen((char *)req->current_query->sname);
138+
printf("Domain name: %s (length: %u)\n", req->current_query->sname, dname_size);
139+
140+
}
141+
}
142+
143+
const kru_price_t dname_price = ((float)dname_size)/100 * (KRU_LIMIT / instant_limit);
144+
//printf("dname_size: %d\n", dname_price/);
145+
146+
for (size_t i = 0; i < V4_PREFIXES_CNT; i++) {
147+
kru_price_t v4_price = dname_price / V4_RATE_MULT[i];
148+
dnamelimiting->v4_prices[i] = v4_price;
149+
// printf("V4 - %ld: %d (dname_size = %d), ratio - %f\n", i, v4_price, dname_size, dname_price/(float)dnamelimiting->v4_prices[i]);
150+
}
151+
152+
for (size_t i = 0; i < V6_PREFIXES_CNT; i++) {
153+
kru_price_t v6_price = dname_price / V6_RATE_MULT[i];
154+
dnamelimiting->v6_prices[i] = v6_price;
155+
// printf("V6 - %ld: %d (dname_size = %d), ratio - %f\n", i, v6_price, dname_size, dname_price/(float)dnamelimiting->v6_prices[i]);
156+
}
157+
158+
const uint32_t time_now = kr_now();
159+
160+
// classify
161+
_Alignas(16) uint8_t key[16] = {0, };
162+
uint8_t limited_prefix;
163+
if (req->qsource.addr->sa_family == AF_INET6) {
164+
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)req->qsource.addr;
165+
memcpy(key, &ipv6->sin6_addr, 16);
166+
167+
// compute adjusted prices, using standard rounding
168+
kru_price_t prices[V6_PREFIXES_CNT];
169+
for (int i = 0; i < V6_PREFIXES_CNT; ++i) {
170+
prices[i] = (req->qsource.price_factor16
171+
* (uint64_t)dnamelimiting->v6_prices[i] + (1<<15)) >> 16;
172+
}
173+
limited_prefix = KRU.limited_multi_prefix_or((struct kru *)dnamelimiting->kru, time_now,
174+
1, key, V6_PREFIXES, prices, V6_PREFIXES_CNT, NULL);
175+
} else {
176+
struct sockaddr_in *ipv4 = (struct sockaddr_in *)req->qsource.addr;
177+
memcpy(key, &ipv4->sin_addr, 4); // TODO append port?
178+
179+
// compute adjusted prices, using standard rounding
180+
kru_price_t prices[V4_PREFIXES_CNT];
181+
for (int i = 0; i < V4_PREFIXES_CNT; ++i) {
182+
prices[i] = (req->qsource.price_factor16
183+
* (uint64_t)dnamelimiting->v4_prices[i] + (1<<15)) >> 16;
184+
}
185+
limited_prefix = KRU.limited_multi_prefix_or((struct kru *)dnamelimiting->kru, time_now,
186+
0, key, V4_PREFIXES, prices, V4_PREFIXES_CNT, NULL);
187+
}
188+
if (!limited_prefix) return false; // not limited
189+
190+
return true;
191+
}

daemon/dnamelimiting.h

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/* Copyright (C) CZ.NIC, z.s.p.o. <[email protected]>
2+
* SPDX-License-Identifier: GPL-3.0-or-later
3+
*/
4+
5+
#include <stdbool.h>
6+
#include "lib/defines.h"
7+
#include "lib/utils.h"
8+
#include "lib/kru.h"
9+
struct kr_request;
10+
11+
/** Initialize rate-limiting with shared mmapped memory.
12+
* The existing data are used if another instance is already using the file
13+
* and it was initialized with the same parameters; it fails on mismatch. */
14+
KR_EXPORT
15+
int dnamelimiting_init(const char *mmap_file, size_t capacity, uint32_t instant_limit,
16+
uint32_t rate_limit, uint16_t slip, uint32_t log_period, bool dry_run);
17+
18+
/** Do rate-limiting, during knot_layer_api::begin. */
19+
KR_EXPORT
20+
bool dnamelimiting_request_begin(struct kr_request *req, kru_price_t instant_limit);
21+
22+
/** Remove mmapped file data if not used by other processes. */
23+
KR_EXPORT
24+
void dnamelimiting_deinit(void);
25+

daemon/dnamelimiting.test/tests.c

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/* Copyright (C) 2024 CZ.NIC, z.s.p.o. <[email protected]>
2+
3+
This program is free software: you can redistribute it and/or modify
4+
it under the terms of the GNU General Public License as published by
5+
the Free Software Foundation, either version 3 of the License, or
6+
(at your option) any later version.
7+
8+
This program is distributed in the hope that it will be useful,
9+
but WITHOUT ANY WARRANTY; without even the implied warranty of
10+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11+
GNU General Public License for more details.
12+
13+
You should have received a copy of the GNU General Public License
14+
along with this program. If not, see <https://www.gnu.org/licenses/>.
15+
*/
16+
17+
static void the_tests(void **state);
18+
19+
#include "./tests.inc.c" // NOLINT(bugprone-suspicious-include)
20+
21+
// defining count_test as macro to let it print usable line number on failure
22+
#define count_test(DESC, EXPECTED_PASSING, MARGIN_FRACT, ...) { \
23+
int _max_diff = (EXPECTED_PASSING) * (MARGIN_FRACT); \
24+
int cnt = _count_test(EXPECTED_PASSING, __VA_ARGS__); \
25+
assert_int_between(cnt, (EXPECTED_PASSING) - _max_diff, (EXPECTED_PASSING) + _max_diff, DESC); }
26+
27+
uint32_t _count_test(int expected_passing, int addr_family, char *addr_format, uint32_t min_value, uint32_t max_value)
28+
{
29+
uint32_t max_queries = expected_passing > 0 ? 2 * expected_passing : -expected_passing;
30+
struct sockaddr_storage addr;
31+
uint8_t wire[KNOT_WIRE_MIN_PKTSIZE] = { 0 };
32+
knot_pkt_t answer = { .wire = wire };
33+
struct kr_request req = {
34+
.qsource.addr = (struct sockaddr *) &addr,
35+
.qsource.price_factor16 = 1 << 16,
36+
.answer = &answer
37+
};
38+
char addr_str[40];
39+
int cnt = -1;
40+
41+
for (size_t i = 0; i < max_queries; i++) {
42+
(void)snprintf(addr_str, sizeof(addr_str), addr_format,
43+
i % (max_value - min_value + 1) + min_value,
44+
i / (max_value - min_value + 1) % 256);
45+
kr_straddr_socket_set((struct sockaddr *) &addr, addr_str, 0);
46+
if (dnamelimiting_request_begin(&req, RRL_INSTANT_LIMIT)) {
47+
cnt = i;
48+
break;
49+
}
50+
}
51+
return cnt;
52+
}
53+
54+
static void the_tests(void **state)
55+
{
56+
57+
}

0 commit comments

Comments
 (0)