Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (C) Internet Systems Consortium, Inc. ("ISC") |
3 | | * |
4 | | * SPDX-License-Identifier: MPL-2.0 |
5 | | * |
6 | | * This Source Code Form is subject to the terms of the Mozilla Public |
7 | | * License, v. 2.0. If a copy of the MPL was not distributed with this |
8 | | * file, you can obtain one at https://mozilla.org/MPL/2.0/. |
9 | | * |
10 | | * See the COPYRIGHT file distributed with this work for additional |
11 | | * information regarding copyright ownership. |
12 | | */ |
13 | | |
14 | | /*! \file */ |
15 | | |
16 | | /* |
17 | | * Rate limit DNS responses. |
18 | | */ |
19 | | |
20 | | /* #define ISC_LIST_CHECKINIT */ |
21 | | |
22 | | #include <inttypes.h> |
23 | | #include <stdbool.h> |
24 | | |
25 | | #include <isc/mem.h> |
26 | | #include <isc/net.h> |
27 | | #include <isc/netaddr.h> |
28 | | #include <isc/result.h> |
29 | | #include <isc/util.h> |
30 | | |
31 | | #include <dns/log.h> |
32 | | #include <dns/name.h> |
33 | | #include <dns/rcode.h> |
34 | | #include <dns/rdataclass.h> |
35 | | #include <dns/rdatatype.h> |
36 | | #include <dns/rrl.h> |
37 | | #include <dns/view.h> |
38 | | #include <dns/zone.h> |
39 | | |
40 | | static void |
41 | | log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, bool early, char *log_buf, |
42 | | unsigned int log_buf_len); |
43 | | |
44 | | /* |
45 | | * Get a modulus for a hash function that is tolerably likely to be |
46 | | * relatively prime to most inputs. Of course, we get a prime for for initial |
47 | | * values not larger than the square of the last prime. We often get a prime |
48 | | * after that. |
49 | | * This works well in practice for hash tables up to at least 100 |
50 | | * times the square of the last prime and better than a multiplicative hash. |
51 | | */ |
52 | | static int |
53 | 0 | hash_divisor(unsigned int initial) { |
54 | 0 | static uint16_t primes[] = { |
55 | 0 | 3, |
56 | 0 | 5, |
57 | 0 | 7, |
58 | 0 | 11, |
59 | 0 | 13, |
60 | 0 | 17, |
61 | 0 | 19, |
62 | 0 | 23, |
63 | 0 | 29, |
64 | 0 | 31, |
65 | 0 | 37, |
66 | 0 | 41, |
67 | 0 | 43, |
68 | 0 | 47, |
69 | 0 | 53, |
70 | 0 | 59, |
71 | 0 | 61, |
72 | 0 | 67, |
73 | 0 | 71, |
74 | 0 | 73, |
75 | 0 | 79, |
76 | 0 | 83, |
77 | 0 | 89, |
78 | 0 | 97, |
79 | | #if 0 |
80 | | 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, |
81 | | 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, |
82 | | 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, |
83 | | 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, |
84 | | 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, |
85 | | 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, |
86 | | 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, |
87 | | 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, |
88 | | 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, |
89 | | 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, |
90 | | 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, |
91 | | 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, |
92 | | #endif /* if 0 */ |
93 | 0 | }; |
94 | 0 | int divisions, tries; |
95 | 0 | unsigned int result; |
96 | 0 | uint16_t *pp, p; |
97 | |
|
98 | 0 | result = initial; |
99 | |
|
100 | 0 | if (primes[sizeof(primes) / sizeof(primes[0]) - 1] >= result) { |
101 | 0 | pp = primes; |
102 | 0 | while (*pp < result) { |
103 | 0 | ++pp; |
104 | 0 | } |
105 | 0 | return (*pp); |
106 | 0 | } |
107 | | |
108 | 0 | if ((result & 1) == 0) { |
109 | 0 | ++result; |
110 | 0 | } |
111 | |
|
112 | 0 | divisions = 0; |
113 | 0 | tries = 1; |
114 | 0 | pp = primes; |
115 | 0 | do { |
116 | 0 | p = *pp++; |
117 | 0 | ++divisions; |
118 | 0 | if ((result % p) == 0) { |
119 | 0 | ++tries; |
120 | 0 | result += 2; |
121 | 0 | pp = primes; |
122 | 0 | } |
123 | 0 | } while (pp < &primes[sizeof(primes) / sizeof(primes[0])]); |
124 | |
|
125 | 0 | if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) { |
126 | 0 | isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, |
127 | 0 | DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG3, |
128 | 0 | "%d hash_divisor() divisions in %d tries" |
129 | 0 | " to get %d from %d", |
130 | 0 | divisions, tries, result, initial); |
131 | 0 | } |
132 | |
|
133 | 0 | return (result); |
134 | 0 | } |
135 | | |
136 | | /* |
137 | | * Convert a timestamp to a number of seconds in the past. |
138 | | */ |
139 | | static int |
140 | 0 | delta_rrl_time(isc_stdtime_t ts, isc_stdtime_t now) { |
141 | 0 | int delta; |
142 | |
|
143 | 0 | delta = now - ts; |
144 | 0 | if (delta >= 0) { |
145 | 0 | return (delta); |
146 | 0 | } |
147 | | |
148 | | /* |
149 | | * The timestamp is in the future. That future might result from |
150 | | * re-ordered requests, because we use timestamps on requests |
151 | | * instead of consulting a clock. Timestamps in the distant future are |
152 | | * assumed to result from clock changes. When the clock changes to |
153 | | * the past, make existing timestamps appear to be in the past. |
154 | | */ |
155 | 0 | if (delta < -DNS_RRL_MAX_TIME_TRAVEL) { |
156 | 0 | return (DNS_RRL_FOREVER); |
157 | 0 | } |
158 | 0 | return (0); |
159 | 0 | } |
160 | | |
161 | | static int |
162 | 0 | get_age(const dns_rrl_t *rrl, const dns_rrl_entry_t *e, isc_stdtime_t now) { |
163 | 0 | if (!e->ts_valid) { |
164 | 0 | return (DNS_RRL_FOREVER); |
165 | 0 | } |
166 | 0 | return (delta_rrl_time(e->ts + rrl->ts_bases[e->ts_gen], now)); |
167 | 0 | } |
168 | | |
169 | | static void |
170 | 0 | set_age(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_stdtime_t now) { |
171 | 0 | dns_rrl_entry_t *e_old; |
172 | 0 | unsigned int ts_gen; |
173 | 0 | int i, ts; |
174 | |
|
175 | 0 | ts_gen = rrl->ts_gen; |
176 | 0 | ts = now - rrl->ts_bases[ts_gen]; |
177 | 0 | if (ts < 0) { |
178 | 0 | if (ts < -DNS_RRL_MAX_TIME_TRAVEL) { |
179 | 0 | ts = DNS_RRL_FOREVER; |
180 | 0 | } else { |
181 | 0 | ts = 0; |
182 | 0 | } |
183 | 0 | } |
184 | | |
185 | | /* |
186 | | * Make a new timestamp base if the current base is too old. |
187 | | * All entries older than DNS_RRL_MAX_WINDOW seconds are ancient, |
188 | | * useless history. Their timestamps can be treated as if they are |
189 | | * all the same. |
190 | | * We only do arithmetic on more recent timestamps, so bases for |
191 | | * older timestamps can be recycled provided the old timestamps are |
192 | | * marked as ancient history. |
193 | | * This loop is almost always very short because most entries are |
194 | | * recycled after one second and any entries that need to be marked |
195 | | * are older than (DNS_RRL_TS_BASES)*DNS_RRL_MAX_TS seconds. |
196 | | */ |
197 | 0 | if (ts >= DNS_RRL_MAX_TS) { |
198 | 0 | ts_gen = (ts_gen + 1) % DNS_RRL_TS_BASES; |
199 | 0 | for (e_old = ISC_LIST_TAIL(rrl->lru), i = 0; |
200 | 0 | e_old != NULL && (e_old->ts_gen == ts_gen || |
201 | 0 | !ISC_LINK_LINKED(e_old, hlink)); |
202 | 0 | e_old = ISC_LIST_PREV(e_old, lru), ++i) |
203 | 0 | { |
204 | 0 | e_old->ts_valid = false; |
205 | 0 | } |
206 | 0 | if (i != 0) { |
207 | 0 | isc_log_write( |
208 | 0 | dns_lctx, DNS_LOGCATEGORY_RRL, |
209 | 0 | DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1, |
210 | 0 | "rrl new time base scanned %d entries" |
211 | 0 | " at %d for %d %d %d %d", |
212 | 0 | i, now, rrl->ts_bases[ts_gen], |
213 | 0 | rrl->ts_bases[(ts_gen + 1) % DNS_RRL_TS_BASES], |
214 | 0 | rrl->ts_bases[(ts_gen + 2) % DNS_RRL_TS_BASES], |
215 | 0 | rrl->ts_bases[(ts_gen + 3) % DNS_RRL_TS_BASES]); |
216 | 0 | } |
217 | 0 | rrl->ts_gen = ts_gen; |
218 | 0 | rrl->ts_bases[ts_gen] = now; |
219 | 0 | ts = 0; |
220 | 0 | } |
221 | |
|
222 | 0 | e->ts_gen = ts_gen; |
223 | 0 | e->ts = ts; |
224 | 0 | e->ts_valid = true; |
225 | 0 | } |
226 | | |
227 | | static isc_result_t |
228 | 0 | expand_entries(dns_rrl_t *rrl, int newsize) { |
229 | 0 | unsigned int bsize; |
230 | 0 | dns_rrl_block_t *b; |
231 | 0 | dns_rrl_entry_t *e; |
232 | 0 | double rate; |
233 | 0 | int i; |
234 | |
|
235 | 0 | if (rrl->num_entries + newsize >= rrl->max_entries && |
236 | 0 | rrl->max_entries != 0) |
237 | 0 | { |
238 | 0 | newsize = rrl->max_entries - rrl->num_entries; |
239 | 0 | if (newsize <= 0) { |
240 | 0 | return (ISC_R_SUCCESS); |
241 | 0 | } |
242 | 0 | } |
243 | | |
244 | | /* |
245 | | * Log expansions so that the user can tune max-table-size |
246 | | * and min-table-size. |
247 | | */ |
248 | 0 | if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) && rrl->hash != NULL) { |
249 | 0 | rate = rrl->probes; |
250 | 0 | if (rrl->searches != 0) { |
251 | 0 | rate /= rrl->searches; |
252 | 0 | } |
253 | 0 | isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, |
254 | 0 | DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, |
255 | 0 | "increase from %d to %d RRL entries with" |
256 | 0 | " %d bins; average search length %.1f", |
257 | 0 | rrl->num_entries, rrl->num_entries + newsize, |
258 | 0 | rrl->hash->length, rate); |
259 | 0 | } |
260 | |
|
261 | 0 | bsize = sizeof(dns_rrl_block_t) + |
262 | 0 | (newsize - 1) * sizeof(dns_rrl_entry_t); |
263 | 0 | b = isc_mem_getx(rrl->mctx, bsize, ISC_MEM_ZERO); |
264 | 0 | b->size = bsize; |
265 | |
|
266 | 0 | e = b->entries; |
267 | 0 | for (i = 0; i < newsize; ++i, ++e) { |
268 | 0 | ISC_LINK_INIT(e, hlink); |
269 | 0 | ISC_LIST_INITANDAPPEND(rrl->lru, e, lru); |
270 | 0 | } |
271 | 0 | rrl->num_entries += newsize; |
272 | 0 | ISC_LIST_INITANDAPPEND(rrl->blocks, b, link); |
273 | |
|
274 | 0 | return (ISC_R_SUCCESS); |
275 | 0 | } |
276 | | |
277 | | static dns_rrl_bin_t * |
278 | 0 | get_bin(dns_rrl_hash_t *hash, unsigned int hval) { |
279 | 0 | INSIST(hash != NULL); |
280 | 0 | return (&hash->bins[hval % hash->length]); |
281 | 0 | } |
282 | | |
283 | | static void |
284 | 0 | free_old_hash(dns_rrl_t *rrl) { |
285 | 0 | dns_rrl_hash_t *old_hash; |
286 | 0 | dns_rrl_bin_t *old_bin; |
287 | 0 | dns_rrl_entry_t *e, *e_next; |
288 | |
|
289 | 0 | old_hash = rrl->old_hash; |
290 | 0 | for (old_bin = &old_hash->bins[0]; |
291 | 0 | old_bin < &old_hash->bins[old_hash->length]; ++old_bin) |
292 | 0 | { |
293 | 0 | for (e = ISC_LIST_HEAD(*old_bin); e != NULL; e = e_next) { |
294 | 0 | e_next = ISC_LIST_NEXT(e, hlink); |
295 | 0 | ISC_LINK_INIT(e, hlink); |
296 | 0 | } |
297 | 0 | } |
298 | |
|
299 | 0 | isc_mem_put(rrl->mctx, old_hash, |
300 | 0 | sizeof(*old_hash) + |
301 | 0 | (old_hash->length - 1) * sizeof(old_hash->bins[0])); |
302 | 0 | rrl->old_hash = NULL; |
303 | 0 | } |
304 | | |
305 | | static isc_result_t |
306 | 0 | expand_rrl_hash(dns_rrl_t *rrl, isc_stdtime_t now) { |
307 | 0 | dns_rrl_hash_t *hash; |
308 | 0 | int old_bins, new_bins, hsize; |
309 | 0 | double rate; |
310 | |
|
311 | 0 | if (rrl->old_hash != NULL) { |
312 | 0 | free_old_hash(rrl); |
313 | 0 | } |
314 | | |
315 | | /* |
316 | | * Most searches fail and so go to the end of the chain. |
317 | | * Use a small hash table load factor. |
318 | | */ |
319 | 0 | old_bins = (rrl->hash == NULL) ? 0 : rrl->hash->length; |
320 | 0 | new_bins = old_bins / 8 + old_bins; |
321 | 0 | if (new_bins < rrl->num_entries) { |
322 | 0 | new_bins = rrl->num_entries; |
323 | 0 | } |
324 | 0 | new_bins = hash_divisor(new_bins); |
325 | |
|
326 | 0 | hsize = sizeof(dns_rrl_hash_t) + (new_bins - 1) * sizeof(hash->bins[0]); |
327 | 0 | hash = isc_mem_getx(rrl->mctx, hsize, ISC_MEM_ZERO); |
328 | 0 | hash->length = new_bins; |
329 | 0 | rrl->hash_gen ^= 1; |
330 | 0 | hash->gen = rrl->hash_gen; |
331 | |
|
332 | 0 | if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) && old_bins != 0) { |
333 | 0 | rate = rrl->probes; |
334 | 0 | if (rrl->searches != 0) { |
335 | 0 | rate /= rrl->searches; |
336 | 0 | } |
337 | 0 | isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, |
338 | 0 | DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, |
339 | 0 | "increase from %d to %d RRL bins for" |
340 | 0 | " %d entries; average search length %.1f", |
341 | 0 | old_bins, new_bins, rrl->num_entries, rate); |
342 | 0 | } |
343 | |
|
344 | 0 | rrl->old_hash = rrl->hash; |
345 | 0 | if (rrl->old_hash != NULL) { |
346 | 0 | rrl->old_hash->check_time = now; |
347 | 0 | } |
348 | 0 | rrl->hash = hash; |
349 | |
|
350 | 0 | return (ISC_R_SUCCESS); |
351 | 0 | } |
352 | | |
353 | | static void |
354 | 0 | ref_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, int probes, isc_stdtime_t now) { |
355 | | /* |
356 | | * Make the entry most recently used. |
357 | | */ |
358 | 0 | if (ISC_LIST_HEAD(rrl->lru) != e) { |
359 | 0 | if (e == rrl->last_logged) { |
360 | 0 | rrl->last_logged = ISC_LIST_PREV(e, lru); |
361 | 0 | } |
362 | 0 | ISC_LIST_UNLINK(rrl->lru, e, lru); |
363 | 0 | ISC_LIST_PREPEND(rrl->lru, e, lru); |
364 | 0 | } |
365 | | |
366 | | /* |
367 | | * Expand the hash table if it is time and necessary. |
368 | | * This will leave the newly referenced entry in a chain in the |
369 | | * old hash table. It will migrate to the new hash table the next |
370 | | * time it is used or be cut loose when the old hash table is destroyed. |
371 | | */ |
372 | 0 | rrl->probes += probes; |
373 | 0 | ++rrl->searches; |
374 | 0 | if (rrl->searches > 100 && |
375 | 0 | delta_rrl_time(rrl->hash->check_time, now) > 1) |
376 | 0 | { |
377 | 0 | if (rrl->probes / rrl->searches > 2) { |
378 | 0 | expand_rrl_hash(rrl, now); |
379 | 0 | } |
380 | 0 | rrl->hash->check_time = now; |
381 | 0 | rrl->probes = 0; |
382 | 0 | rrl->searches = 0; |
383 | 0 | } |
384 | 0 | } |
385 | | |
386 | | static bool |
387 | 0 | key_cmp(const dns_rrl_key_t *a, const dns_rrl_key_t *b) { |
388 | 0 | if (memcmp(a, b, sizeof(dns_rrl_key_t)) == 0) { |
389 | 0 | return (true); |
390 | 0 | } |
391 | 0 | return (false); |
392 | 0 | } |
393 | | |
394 | | static uint32_t |
395 | 0 | hash_key(const dns_rrl_key_t *key) { |
396 | 0 | uint32_t hval; |
397 | 0 | int i; |
398 | |
|
399 | 0 | hval = key->w[0]; |
400 | 0 | for (i = sizeof(key->w) / sizeof(key->w[0]) - 1; i >= 0; --i) { |
401 | 0 | hval = key->w[i] + (hval << 1); |
402 | 0 | } |
403 | 0 | return (hval); |
404 | 0 | } |
405 | | |
406 | | /* |
407 | | * Construct the hash table key. |
408 | | * Use a hash of the DNS query name to save space in the database. |
409 | | * Collisions result in legitimate rate limiting responses for one |
410 | | * query name also limiting responses for other names to the |
411 | | * same client. This is rare and benign enough given the large |
412 | | * space costs compared to keeping the entire name in the database |
413 | | * entry or the time costs of dynamic allocation. |
414 | | */ |
415 | | static void |
416 | | make_key(const dns_rrl_t *rrl, dns_rrl_key_t *key, |
417 | | const isc_sockaddr_t *client_addr, dns_zone_t *zone, |
418 | | dns_rdatatype_t qtype, const dns_name_t *qname, |
419 | 0 | dns_rdataclass_t qclass, dns_rrl_rtype_t rtype) { |
420 | 0 | int i; |
421 | |
|
422 | 0 | memset(key, 0, sizeof(*key)); |
423 | |
|
424 | 0 | key->s.rtype = rtype; |
425 | 0 | if (rtype == DNS_RRL_RTYPE_QUERY) { |
426 | 0 | key->s.qtype = qtype; |
427 | 0 | key->s.qclass = qclass & 0xff; |
428 | 0 | } else if (rtype == DNS_RRL_RTYPE_REFERRAL || |
429 | 0 | rtype == DNS_RRL_RTYPE_NODATA) |
430 | 0 | { |
431 | | /* |
432 | | * Because there is no qtype in the empty answer sections of |
433 | | * referral and NODATA responses, count them as the same. |
434 | | */ |
435 | 0 | key->s.qclass = qclass & 0xff; |
436 | 0 | } |
437 | |
|
438 | 0 | if (qname != NULL && qname->labels != 0) { |
439 | 0 | dns_name_t *origin = NULL; |
440 | |
|
441 | 0 | if (qname->attributes.wildcard && zone != NULL && |
442 | 0 | (origin = dns_zone_getorigin(zone)) != NULL) |
443 | 0 | { |
444 | 0 | dns_fixedname_t fixed; |
445 | 0 | dns_name_t *wild; |
446 | 0 | isc_result_t result; |
447 | | |
448 | | /* |
449 | | * Put all wildcard names in one bucket using the zone's |
450 | | * origin name concatenated to the "*" name. |
451 | | */ |
452 | 0 | wild = dns_fixedname_initname(&fixed); |
453 | 0 | result = dns_name_concatenate(dns_wildcardname, origin, |
454 | 0 | wild, NULL); |
455 | 0 | if (result != ISC_R_SUCCESS) { |
456 | | /* |
457 | | * Fallback to use the zone's origin name |
458 | | * instead of the concatenated name. |
459 | | */ |
460 | 0 | wild = origin; |
461 | 0 | } |
462 | 0 | key->s.qname_hash = dns_name_fullhash(wild, false); |
463 | 0 | } else { |
464 | 0 | key->s.qname_hash = dns_name_fullhash(qname, false); |
465 | 0 | } |
466 | 0 | } |
467 | |
|
468 | 0 | switch (client_addr->type.sa.sa_family) { |
469 | 0 | case AF_INET: |
470 | 0 | key->s.ip[0] = (client_addr->type.sin.sin_addr.s_addr & |
471 | 0 | rrl->ipv4_mask); |
472 | 0 | break; |
473 | 0 | case AF_INET6: |
474 | 0 | key->s.ipv6 = true; |
475 | 0 | memmove(key->s.ip, &client_addr->type.sin6.sin6_addr, |
476 | 0 | sizeof(key->s.ip)); |
477 | 0 | for (i = 0; i < DNS_RRL_MAX_PREFIX / 32; ++i) { |
478 | 0 | key->s.ip[i] &= rrl->ipv6_mask[i]; |
479 | 0 | } |
480 | 0 | break; |
481 | 0 | } |
482 | 0 | } |
483 | | |
484 | | static dns_rrl_rate_t * |
485 | 0 | get_rate(dns_rrl_t *rrl, dns_rrl_rtype_t rtype) { |
486 | 0 | switch (rtype) { |
487 | 0 | case DNS_RRL_RTYPE_QUERY: |
488 | 0 | return (&rrl->responses_per_second); |
489 | 0 | case DNS_RRL_RTYPE_REFERRAL: |
490 | 0 | return (&rrl->referrals_per_second); |
491 | 0 | case DNS_RRL_RTYPE_NODATA: |
492 | 0 | return (&rrl->nodata_per_second); |
493 | 0 | case DNS_RRL_RTYPE_NXDOMAIN: |
494 | 0 | return (&rrl->nxdomains_per_second); |
495 | 0 | case DNS_RRL_RTYPE_ERROR: |
496 | 0 | return (&rrl->errors_per_second); |
497 | 0 | case DNS_RRL_RTYPE_ALL: |
498 | 0 | return (&rrl->all_per_second); |
499 | 0 | default: |
500 | 0 | UNREACHABLE(); |
501 | 0 | } |
502 | 0 | } |
503 | | |
504 | | static int |
505 | 0 | response_balance(dns_rrl_t *rrl, const dns_rrl_entry_t *e, int age) { |
506 | 0 | dns_rrl_rate_t *ratep; |
507 | 0 | int balance, rate; |
508 | |
|
509 | 0 | if (e->key.s.rtype == DNS_RRL_RTYPE_TCP) { |
510 | 0 | rate = 1; |
511 | 0 | } else { |
512 | 0 | ratep = get_rate(rrl, e->key.s.rtype); |
513 | 0 | rate = ratep->scaled; |
514 | 0 | } |
515 | |
|
516 | 0 | balance = e->responses + age * rate; |
517 | 0 | if (balance > rate) { |
518 | 0 | balance = rate; |
519 | 0 | } |
520 | 0 | return (balance); |
521 | 0 | } |
522 | | |
523 | | /* |
524 | | * Search for an entry for a response and optionally create it. |
525 | | */ |
526 | | static dns_rrl_entry_t * |
527 | | get_entry(dns_rrl_t *rrl, const isc_sockaddr_t *client_addr, dns_zone_t *zone, |
528 | | dns_rdataclass_t qclass, dns_rdatatype_t qtype, |
529 | | const dns_name_t *qname, dns_rrl_rtype_t rtype, isc_stdtime_t now, |
530 | 0 | bool create, char *log_buf, unsigned int log_buf_len) { |
531 | 0 | dns_rrl_key_t key; |
532 | 0 | uint32_t hval; |
533 | 0 | dns_rrl_entry_t *e; |
534 | 0 | dns_rrl_hash_t *hash; |
535 | 0 | dns_rrl_bin_t *new_bin, *old_bin; |
536 | 0 | int probes, age; |
537 | |
|
538 | 0 | make_key(rrl, &key, client_addr, zone, qtype, qname, qclass, rtype); |
539 | 0 | hval = hash_key(&key); |
540 | | |
541 | | /* |
542 | | * Look for the entry in the current hash table. |
543 | | */ |
544 | 0 | new_bin = get_bin(rrl->hash, hval); |
545 | 0 | probes = 1; |
546 | 0 | e = ISC_LIST_HEAD(*new_bin); |
547 | 0 | while (e != NULL) { |
548 | 0 | if (key_cmp(&e->key, &key)) { |
549 | 0 | ref_entry(rrl, e, probes, now); |
550 | 0 | return (e); |
551 | 0 | } |
552 | 0 | ++probes; |
553 | 0 | e = ISC_LIST_NEXT(e, hlink); |
554 | 0 | } |
555 | | |
556 | | /* |
557 | | * Look in the old hash table. |
558 | | */ |
559 | 0 | if (rrl->old_hash != NULL) { |
560 | 0 | old_bin = get_bin(rrl->old_hash, hval); |
561 | 0 | e = ISC_LIST_HEAD(*old_bin); |
562 | 0 | while (e != NULL) { |
563 | 0 | if (key_cmp(&e->key, &key)) { |
564 | 0 | ISC_LIST_UNLINK(*old_bin, e, hlink); |
565 | 0 | ISC_LIST_PREPEND(*new_bin, e, hlink); |
566 | 0 | e->hash_gen = rrl->hash_gen; |
567 | 0 | ref_entry(rrl, e, probes, now); |
568 | 0 | return (e); |
569 | 0 | } |
570 | 0 | e = ISC_LIST_NEXT(e, hlink); |
571 | 0 | } |
572 | | |
573 | | /* |
574 | | * Discard previous hash table when all of its entries are old. |
575 | | */ |
576 | 0 | age = delta_rrl_time(rrl->old_hash->check_time, now); |
577 | 0 | if (age > rrl->window) { |
578 | 0 | free_old_hash(rrl); |
579 | 0 | } |
580 | 0 | } |
581 | | |
582 | 0 | if (!create) { |
583 | 0 | return (NULL); |
584 | 0 | } |
585 | | |
586 | | /* |
587 | | * The entry does not exist, so create it by finding a free entry. |
588 | | * Keep currently penalized and logged entries. |
589 | | * Try to make more entries if none are idle. |
590 | | * Steal the oldest entry if we cannot create more. |
591 | | */ |
592 | 0 | for (e = ISC_LIST_TAIL(rrl->lru); e != NULL; e = ISC_LIST_PREV(e, lru)) |
593 | 0 | { |
594 | 0 | if (!ISC_LINK_LINKED(e, hlink)) { |
595 | 0 | break; |
596 | 0 | } |
597 | 0 | age = get_age(rrl, e, now); |
598 | 0 | if (age <= 1) { |
599 | 0 | e = NULL; |
600 | 0 | break; |
601 | 0 | } |
602 | 0 | if (!e->logged && response_balance(rrl, e, age) > 0) { |
603 | 0 | break; |
604 | 0 | } |
605 | 0 | } |
606 | 0 | if (e == NULL) { |
607 | 0 | expand_entries(rrl, ISC_MIN((rrl->num_entries + 1) / 2, 1000)); |
608 | 0 | e = ISC_LIST_TAIL(rrl->lru); |
609 | 0 | } |
610 | 0 | if (e->logged) { |
611 | 0 | log_end(rrl, e, true, log_buf, log_buf_len); |
612 | 0 | } |
613 | 0 | if (ISC_LINK_LINKED(e, hlink)) { |
614 | 0 | if (e->hash_gen == rrl->hash_gen) { |
615 | 0 | hash = rrl->hash; |
616 | 0 | } else { |
617 | 0 | hash = rrl->old_hash; |
618 | 0 | } |
619 | 0 | old_bin = get_bin(hash, hash_key(&e->key)); |
620 | 0 | ISC_LIST_UNLINK(*old_bin, e, hlink); |
621 | 0 | } |
622 | 0 | ISC_LIST_PREPEND(*new_bin, e, hlink); |
623 | 0 | e->hash_gen = rrl->hash_gen; |
624 | 0 | e->key = key; |
625 | 0 | e->ts_valid = false; |
626 | 0 | ref_entry(rrl, e, probes, now); |
627 | 0 | return (e); |
628 | 0 | } |
629 | | |
630 | | static void |
631 | 0 | debit_log(const dns_rrl_entry_t *e, int age, const char *action) { |
632 | 0 | char buf[sizeof("age=2147483647")]; |
633 | 0 | const char *age_str; |
634 | |
|
635 | 0 | if (age == DNS_RRL_FOREVER) { |
636 | 0 | age_str = ""; |
637 | 0 | } else { |
638 | 0 | snprintf(buf, sizeof(buf), "age=%d", age); |
639 | 0 | age_str = buf; |
640 | 0 | } |
641 | 0 | isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, DNS_LOGMODULE_REQUEST, |
642 | 0 | DNS_RRL_LOG_DEBUG3, "rrl %08x %6s responses=%-3d %s", |
643 | 0 | hash_key(&e->key), age_str, e->responses, action); |
644 | 0 | } |
645 | | |
646 | | static dns_rrl_result_t |
647 | | debit_rrl_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, double qps, double scale, |
648 | | const isc_sockaddr_t *client_addr, isc_stdtime_t now, |
649 | 0 | char *log_buf, unsigned int log_buf_len) { |
650 | 0 | int rate, new_rate, slip, new_slip, age, log_secs, min; |
651 | 0 | dns_rrl_rate_t *ratep; |
652 | 0 | dns_rrl_entry_t const *credit_e; |
653 | | |
654 | | /* |
655 | | * Pick the rate counter. |
656 | | * Optionally adjust the rate by the estimated query/second rate. |
657 | | */ |
658 | 0 | ratep = get_rate(rrl, e->key.s.rtype); |
659 | 0 | rate = ratep->r; |
660 | 0 | if (rate == 0) { |
661 | 0 | return (DNS_RRL_RESULT_OK); |
662 | 0 | } |
663 | | |
664 | 0 | if (scale < 1.0) { |
665 | | /* |
666 | | * The limit for clients that have used TCP is not scaled. |
667 | | */ |
668 | 0 | credit_e = get_entry( |
669 | 0 | rrl, client_addr, NULL, 0, dns_rdatatype_none, NULL, |
670 | 0 | DNS_RRL_RTYPE_TCP, now, false, log_buf, log_buf_len); |
671 | 0 | if (credit_e != NULL) { |
672 | 0 | age = get_age(rrl, e, now); |
673 | 0 | if (age < rrl->window) { |
674 | 0 | scale = 1.0; |
675 | 0 | } |
676 | 0 | } |
677 | 0 | } |
678 | 0 | if (scale < 1.0) { |
679 | 0 | new_rate = (int)(rate * scale); |
680 | 0 | if (new_rate < 1) { |
681 | 0 | new_rate = 1; |
682 | 0 | } |
683 | 0 | if (ratep->scaled != new_rate) { |
684 | 0 | isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, |
685 | 0 | DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1, |
686 | 0 | "%d qps scaled %s by %.2f" |
687 | 0 | " from %d to %d", |
688 | 0 | (int)qps, ratep->str, scale, rate, |
689 | 0 | new_rate); |
690 | 0 | rate = new_rate; |
691 | 0 | ratep->scaled = rate; |
692 | 0 | } |
693 | 0 | } |
694 | |
|
695 | 0 | min = -rrl->window * rate; |
696 | | |
697 | | /* |
698 | | * Treat time jumps into the recent past as no time. |
699 | | * Treat entries older than the window as if they were just created |
700 | | * Credit other entries. |
701 | | */ |
702 | 0 | age = get_age(rrl, e, now); |
703 | 0 | if (age > 0) { |
704 | | /* |
705 | | * Credit tokens earned during elapsed time. |
706 | | */ |
707 | 0 | if (age > rrl->window) { |
708 | 0 | e->responses = rate; |
709 | 0 | e->slip_cnt = 0; |
710 | 0 | } else { |
711 | 0 | e->responses += rate * age; |
712 | 0 | if (e->responses > rate) { |
713 | 0 | e->responses = rate; |
714 | 0 | e->slip_cnt = 0; |
715 | 0 | } |
716 | 0 | } |
717 | | /* |
718 | | * Find the seconds since last log message without overflowing |
719 | | * small counter. This counter is reset when an entry is |
720 | | * created. It is not necessarily reset when some requests |
721 | | * are answered provided other requests continue to be dropped |
722 | | * or slipped. This can happen when the request rate is just |
723 | | * at the limit. |
724 | | */ |
725 | 0 | if (e->logged) { |
726 | 0 | log_secs = e->log_secs; |
727 | 0 | log_secs += age; |
728 | 0 | if (log_secs > DNS_RRL_MAX_LOG_SECS || log_secs < 0) { |
729 | 0 | log_secs = DNS_RRL_MAX_LOG_SECS; |
730 | 0 | } |
731 | 0 | e->log_secs = log_secs; |
732 | 0 | } |
733 | 0 | } |
734 | 0 | set_age(rrl, e, now); |
735 | | |
736 | | /* |
737 | | * Debit the entry for this response. |
738 | | */ |
739 | 0 | if (--e->responses >= 0) { |
740 | 0 | if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) { |
741 | 0 | debit_log(e, age, ""); |
742 | 0 | } |
743 | 0 | return (DNS_RRL_RESULT_OK); |
744 | 0 | } |
745 | | |
746 | 0 | if (e->responses < min) { |
747 | 0 | e->responses = min; |
748 | 0 | } |
749 | | |
750 | | /* |
751 | | * Drop this response unless it should slip or leak. |
752 | | */ |
753 | 0 | slip = rrl->slip.r; |
754 | 0 | if (slip > 2 && scale < 1.0) { |
755 | 0 | new_slip = (int)(slip * scale); |
756 | 0 | if (new_slip < 2) { |
757 | 0 | new_slip = 2; |
758 | 0 | } |
759 | 0 | if (rrl->slip.scaled != new_slip) { |
760 | 0 | isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, |
761 | 0 | DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1, |
762 | 0 | "%d qps scaled slip" |
763 | 0 | " by %.2f from %d to %d", |
764 | 0 | (int)qps, scale, slip, new_slip); |
765 | 0 | slip = new_slip; |
766 | 0 | rrl->slip.scaled = slip; |
767 | 0 | } |
768 | 0 | } |
769 | 0 | if (slip != 0 && e->key.s.rtype != DNS_RRL_RTYPE_ALL) { |
770 | 0 | if (e->slip_cnt++ == 0) { |
771 | 0 | if ((int)e->slip_cnt >= slip) { |
772 | 0 | e->slip_cnt = 0; |
773 | 0 | } |
774 | 0 | if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) { |
775 | 0 | debit_log(e, age, "slip"); |
776 | 0 | } |
777 | 0 | return (DNS_RRL_RESULT_SLIP); |
778 | 0 | } else if ((int)e->slip_cnt >= slip) { |
779 | 0 | e->slip_cnt = 0; |
780 | 0 | } |
781 | 0 | } |
782 | | |
783 | 0 | if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3)) { |
784 | 0 | debit_log(e, age, "drop"); |
785 | 0 | } |
786 | 0 | return (DNS_RRL_RESULT_DROP); |
787 | 0 | } |
788 | | |
789 | | static dns_rrl_qname_buf_t * |
790 | 0 | get_qname(dns_rrl_t *rrl, const dns_rrl_entry_t *e) { |
791 | 0 | dns_rrl_qname_buf_t *qbuf; |
792 | |
|
793 | 0 | qbuf = rrl->qnames[e->log_qname]; |
794 | 0 | if (qbuf == NULL || qbuf->e != e) { |
795 | 0 | return (NULL); |
796 | 0 | } |
797 | 0 | return (qbuf); |
798 | 0 | } |
799 | | |
800 | | static void |
801 | 0 | free_qname(dns_rrl_t *rrl, dns_rrl_entry_t *e) { |
802 | 0 | dns_rrl_qname_buf_t *qbuf; |
803 | |
|
804 | 0 | qbuf = get_qname(rrl, e); |
805 | 0 | if (qbuf != NULL) { |
806 | 0 | qbuf->e = NULL; |
807 | 0 | ISC_LIST_APPEND(rrl->qname_free, qbuf, link); |
808 | 0 | } |
809 | 0 | } |
810 | | |
811 | | static void |
812 | 0 | add_log_str(isc_buffer_t *lb, const char *str, unsigned int str_len) { |
813 | 0 | isc_region_t region; |
814 | |
|
815 | 0 | isc_buffer_availableregion(lb, ®ion); |
816 | 0 | if (str_len >= region.length) { |
817 | 0 | if (region.length == 0U) { |
818 | 0 | return; |
819 | 0 | } |
820 | 0 | str_len = region.length; |
821 | 0 | } |
822 | 0 | memmove(region.base, str, str_len); |
823 | 0 | isc_buffer_add(lb, str_len); |
824 | 0 | } |
825 | | |
826 | 0 | #define ADD_LOG_CSTR(eb, s) add_log_str(eb, s, sizeof(s) - 1) |
827 | | |
828 | | /* |
829 | | * Build strings for the logs |
830 | | */ |
831 | | static void |
832 | | make_log_buf(dns_rrl_t *rrl, dns_rrl_entry_t *e, const char *str1, |
833 | | const char *str2, bool plural, const dns_name_t *qname, |
834 | | bool save_qname, dns_rrl_result_t rrl_result, |
835 | | isc_result_t resp_result, char *log_buf, |
836 | 0 | unsigned int log_buf_len) { |
837 | 0 | isc_buffer_t lb; |
838 | 0 | dns_rrl_qname_buf_t *qbuf; |
839 | 0 | isc_netaddr_t cidr; |
840 | 0 | char strbuf[ISC_MAX(sizeof("/123"), sizeof(" (12345678)"))]; |
841 | 0 | const char *rstr; |
842 | 0 | isc_result_t msg_result; |
843 | |
|
844 | 0 | if (log_buf_len <= 1) { |
845 | 0 | if (log_buf_len == 1) { |
846 | 0 | log_buf[0] = '\0'; |
847 | 0 | } |
848 | 0 | return; |
849 | 0 | } |
850 | 0 | isc_buffer_init(&lb, log_buf, log_buf_len - 1); |
851 | |
|
852 | 0 | if (str1 != NULL) { |
853 | 0 | add_log_str(&lb, str1, strlen(str1)); |
854 | 0 | } |
855 | 0 | if (str2 != NULL) { |
856 | 0 | add_log_str(&lb, str2, strlen(str2)); |
857 | 0 | } |
858 | |
|
859 | 0 | switch (rrl_result) { |
860 | 0 | case DNS_RRL_RESULT_OK: |
861 | 0 | break; |
862 | 0 | case DNS_RRL_RESULT_DROP: |
863 | 0 | ADD_LOG_CSTR(&lb, "drop "); |
864 | 0 | break; |
865 | 0 | case DNS_RRL_RESULT_SLIP: |
866 | 0 | ADD_LOG_CSTR(&lb, "slip "); |
867 | 0 | break; |
868 | 0 | default: |
869 | 0 | UNREACHABLE(); |
870 | 0 | } |
871 | | |
872 | 0 | switch (e->key.s.rtype) { |
873 | 0 | case DNS_RRL_RTYPE_QUERY: |
874 | 0 | break; |
875 | 0 | case DNS_RRL_RTYPE_REFERRAL: |
876 | 0 | ADD_LOG_CSTR(&lb, "referral "); |
877 | 0 | break; |
878 | 0 | case DNS_RRL_RTYPE_NODATA: |
879 | 0 | ADD_LOG_CSTR(&lb, "NODATA "); |
880 | 0 | break; |
881 | 0 | case DNS_RRL_RTYPE_NXDOMAIN: |
882 | 0 | ADD_LOG_CSTR(&lb, "NXDOMAIN "); |
883 | 0 | break; |
884 | 0 | case DNS_RRL_RTYPE_ERROR: |
885 | 0 | if (resp_result == ISC_R_SUCCESS) { |
886 | 0 | ADD_LOG_CSTR(&lb, "error "); |
887 | 0 | } else { |
888 | 0 | rstr = isc_result_totext(resp_result); |
889 | 0 | add_log_str(&lb, rstr, strlen(rstr)); |
890 | 0 | ADD_LOG_CSTR(&lb, " error "); |
891 | 0 | } |
892 | 0 | break; |
893 | 0 | case DNS_RRL_RTYPE_ALL: |
894 | 0 | ADD_LOG_CSTR(&lb, "all "); |
895 | 0 | break; |
896 | 0 | default: |
897 | 0 | UNREACHABLE(); |
898 | 0 | } |
899 | | |
900 | 0 | if (plural) { |
901 | 0 | ADD_LOG_CSTR(&lb, "responses to "); |
902 | 0 | } else { |
903 | 0 | ADD_LOG_CSTR(&lb, "response to "); |
904 | 0 | } |
905 | |
|
906 | 0 | memset(&cidr, 0, sizeof(cidr)); |
907 | 0 | if (e->key.s.ipv6) { |
908 | 0 | snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv6_prefixlen); |
909 | 0 | cidr.family = AF_INET6; |
910 | 0 | memset(&cidr.type.in6, 0, sizeof(cidr.type.in6)); |
911 | 0 | memmove(&cidr.type.in6, e->key.s.ip, sizeof(e->key.s.ip)); |
912 | 0 | } else { |
913 | 0 | snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv4_prefixlen); |
914 | 0 | cidr.family = AF_INET; |
915 | 0 | cidr.type.in.s_addr = e->key.s.ip[0]; |
916 | 0 | } |
917 | 0 | msg_result = isc_netaddr_totext(&cidr, &lb); |
918 | 0 | if (msg_result != ISC_R_SUCCESS) { |
919 | 0 | ADD_LOG_CSTR(&lb, "?"); |
920 | 0 | } |
921 | 0 | add_log_str(&lb, strbuf, strlen(strbuf)); |
922 | |
|
923 | 0 | if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY || |
924 | 0 | e->key.s.rtype == DNS_RRL_RTYPE_REFERRAL || |
925 | 0 | e->key.s.rtype == DNS_RRL_RTYPE_NODATA || |
926 | 0 | e->key.s.rtype == DNS_RRL_RTYPE_NXDOMAIN) |
927 | 0 | { |
928 | 0 | qbuf = get_qname(rrl, e); |
929 | 0 | if (save_qname && qbuf == NULL && qname != NULL && |
930 | 0 | dns_name_isabsolute(qname)) |
931 | 0 | { |
932 | | /* |
933 | | * Capture the qname for the "stop limiting" message. |
934 | | */ |
935 | 0 | qbuf = ISC_LIST_TAIL(rrl->qname_free); |
936 | 0 | if (qbuf != NULL) { |
937 | 0 | ISC_LIST_UNLINK(rrl->qname_free, qbuf, link); |
938 | 0 | } else if (rrl->num_qnames < DNS_RRL_QNAMES) { |
939 | 0 | qbuf = isc_mem_get(rrl->mctx, sizeof(*qbuf)); |
940 | 0 | *qbuf = (dns_rrl_qname_buf_t){ |
941 | 0 | .index = rrl->num_qnames, |
942 | 0 | }; |
943 | 0 | ISC_LINK_INIT(qbuf, link); |
944 | 0 | rrl->qnames[rrl->num_qnames++] = qbuf; |
945 | 0 | } |
946 | 0 | if (qbuf != NULL) { |
947 | 0 | e->log_qname = qbuf->index; |
948 | 0 | qbuf->e = e; |
949 | 0 | dns_fixedname_init(&qbuf->qname); |
950 | 0 | dns_name_copy(qname, |
951 | 0 | dns_fixedname_name(&qbuf->qname)); |
952 | 0 | } |
953 | 0 | } |
954 | 0 | if (qbuf != NULL) { |
955 | 0 | qname = dns_fixedname_name(&qbuf->qname); |
956 | 0 | } |
957 | 0 | if (qname != NULL) { |
958 | 0 | ADD_LOG_CSTR(&lb, " for "); |
959 | 0 | (void)dns_name_totext(qname, true, &lb); |
960 | 0 | } else { |
961 | 0 | ADD_LOG_CSTR(&lb, " for (?)"); |
962 | 0 | } |
963 | 0 | if (e->key.s.rtype != DNS_RRL_RTYPE_NXDOMAIN) { |
964 | 0 | ADD_LOG_CSTR(&lb, " "); |
965 | 0 | (void)dns_rdataclass_totext(e->key.s.qclass, &lb); |
966 | 0 | if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY) { |
967 | 0 | ADD_LOG_CSTR(&lb, " "); |
968 | 0 | (void)dns_rdatatype_totext(e->key.s.qtype, &lb); |
969 | 0 | } |
970 | 0 | } |
971 | 0 | snprintf(strbuf, sizeof(strbuf), " (%08" PRIx32 ")", |
972 | 0 | e->key.s.qname_hash); |
973 | 0 | add_log_str(&lb, strbuf, strlen(strbuf)); |
974 | 0 | } |
975 | | |
976 | | /* |
977 | | * We saved room for '\0'. |
978 | | */ |
979 | 0 | log_buf[isc_buffer_usedlength(&lb)] = '\0'; |
980 | 0 | } |
981 | | |
982 | | static void |
983 | | log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, bool early, char *log_buf, |
984 | 0 | unsigned int log_buf_len) { |
985 | 0 | if (e->logged) { |
986 | 0 | make_log_buf(rrl, e, early ? "*" : NULL, |
987 | 0 | rrl->log_only ? "would stop limiting " |
988 | 0 | : "stop limiting ", |
989 | 0 | true, NULL, false, DNS_RRL_RESULT_OK, |
990 | 0 | ISC_R_SUCCESS, log_buf, log_buf_len); |
991 | 0 | isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, |
992 | 0 | DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, "%s", |
993 | 0 | log_buf); |
994 | 0 | free_qname(rrl, e); |
995 | 0 | e->logged = false; |
996 | 0 | --rrl->num_logged; |
997 | 0 | } |
998 | 0 | } |
999 | | |
1000 | | /* |
1001 | | * Log messages for streams that have stopped being rate limited. |
1002 | | */ |
1003 | | static void |
1004 | | log_stops(dns_rrl_t *rrl, isc_stdtime_t now, int limit, char *log_buf, |
1005 | 0 | unsigned int log_buf_len) { |
1006 | 0 | dns_rrl_entry_t *e; |
1007 | 0 | int age; |
1008 | |
|
1009 | 0 | for (e = rrl->last_logged; e != NULL; e = ISC_LIST_PREV(e, lru)) { |
1010 | 0 | if (!e->logged) { |
1011 | 0 | continue; |
1012 | 0 | } |
1013 | 0 | if (now != 0) { |
1014 | 0 | age = get_age(rrl, e, now); |
1015 | 0 | if (age < DNS_RRL_STOP_LOG_SECS || |
1016 | 0 | response_balance(rrl, e, age) < 0) |
1017 | 0 | { |
1018 | 0 | break; |
1019 | 0 | } |
1020 | 0 | } |
1021 | | |
1022 | 0 | log_end(rrl, e, now == 0, log_buf, log_buf_len); |
1023 | 0 | if (rrl->num_logged <= 0) { |
1024 | 0 | break; |
1025 | 0 | } |
1026 | | |
1027 | | /* |
1028 | | * Too many messages could stall real work. |
1029 | | */ |
1030 | 0 | if (--limit < 0) { |
1031 | 0 | rrl->last_logged = ISC_LIST_PREV(e, lru); |
1032 | 0 | return; |
1033 | 0 | } |
1034 | 0 | } |
1035 | 0 | if (e == NULL) { |
1036 | 0 | INSIST(rrl->num_logged == 0); |
1037 | 0 | rrl->log_stops_time = now; |
1038 | 0 | } |
1039 | 0 | rrl->last_logged = e; |
1040 | 0 | } |
1041 | | |
1042 | | /* |
1043 | | * Main rate limit interface. |
1044 | | */ |
1045 | | dns_rrl_result_t |
1046 | | dns_rrl(dns_view_t *view, dns_zone_t *zone, const isc_sockaddr_t *client_addr, |
1047 | | bool is_tcp, dns_rdataclass_t qclass, dns_rdatatype_t qtype, |
1048 | | const dns_name_t *qname, isc_result_t resp_result, isc_stdtime_t now, |
1049 | 0 | bool wouldlog, char *log_buf, unsigned int log_buf_len) { |
1050 | 0 | dns_rrl_t *rrl; |
1051 | 0 | dns_rrl_rtype_t rtype; |
1052 | 0 | dns_rrl_entry_t *e; |
1053 | 0 | isc_netaddr_t netclient; |
1054 | 0 | int secs; |
1055 | 0 | double qps, scale; |
1056 | 0 | int exempt_match; |
1057 | 0 | isc_result_t result; |
1058 | 0 | dns_rrl_result_t rrl_result; |
1059 | |
|
1060 | 0 | INSIST(log_buf != NULL && log_buf_len > 0); |
1061 | |
|
1062 | 0 | rrl = view->rrl; |
1063 | 0 | if (rrl->exempt != NULL) { |
1064 | 0 | isc_netaddr_fromsockaddr(&netclient, client_addr); |
1065 | 0 | result = dns_acl_match(&netclient, NULL, rrl->exempt, |
1066 | 0 | view->aclenv, &exempt_match, NULL); |
1067 | 0 | if (result == ISC_R_SUCCESS && exempt_match > 0) { |
1068 | 0 | return (DNS_RRL_RESULT_OK); |
1069 | 0 | } |
1070 | 0 | } |
1071 | | |
1072 | 0 | LOCK(&rrl->lock); |
1073 | | |
1074 | | /* |
1075 | | * Estimate total query per second rate when scaling by qps. |
1076 | | */ |
1077 | 0 | if (rrl->qps_scale == 0) { |
1078 | 0 | qps = 0.0; |
1079 | 0 | scale = 1.0; |
1080 | 0 | } else { |
1081 | 0 | ++rrl->qps_responses; |
1082 | 0 | secs = delta_rrl_time(rrl->qps_time, now); |
1083 | 0 | if (secs <= 0) { |
1084 | 0 | qps = rrl->qps; |
1085 | 0 | } else { |
1086 | 0 | qps = (1.0 * rrl->qps_responses) / secs; |
1087 | 0 | if (secs >= rrl->window) { |
1088 | 0 | if (isc_log_wouldlog(dns_lctx, |
1089 | 0 | DNS_RRL_LOG_DEBUG3)) |
1090 | 0 | { |
1091 | 0 | isc_log_write(dns_lctx, |
1092 | 0 | DNS_LOGCATEGORY_RRL, |
1093 | 0 | DNS_LOGMODULE_REQUEST, |
1094 | 0 | DNS_RRL_LOG_DEBUG3, |
1095 | 0 | "%d responses/%d seconds" |
1096 | 0 | " = %d qps", |
1097 | 0 | rrl->qps_responses, secs, |
1098 | 0 | (int)qps); |
1099 | 0 | } |
1100 | 0 | rrl->qps = qps; |
1101 | 0 | rrl->qps_responses = 0; |
1102 | 0 | rrl->qps_time = now; |
1103 | 0 | } else if (qps < rrl->qps) { |
1104 | 0 | qps = rrl->qps; |
1105 | 0 | } |
1106 | 0 | } |
1107 | 0 | scale = rrl->qps_scale / qps; |
1108 | 0 | } |
1109 | | |
1110 | | /* |
1111 | | * Do maintenance once per second. |
1112 | | */ |
1113 | 0 | if (rrl->num_logged > 0 && rrl->log_stops_time != now) { |
1114 | 0 | log_stops(rrl, now, 8, log_buf, log_buf_len); |
1115 | 0 | } |
1116 | | |
1117 | | /* |
1118 | | * Notice TCP responses when scaling limits by qps. |
1119 | | * Do not try to rate limit TCP responses. |
1120 | | */ |
1121 | 0 | if (is_tcp) { |
1122 | 0 | if (scale < 1.0) { |
1123 | 0 | e = get_entry(rrl, client_addr, NULL, 0, |
1124 | 0 | dns_rdatatype_none, NULL, |
1125 | 0 | DNS_RRL_RTYPE_TCP, now, true, log_buf, |
1126 | 0 | log_buf_len); |
1127 | 0 | if (e != NULL) { |
1128 | 0 | e->responses = -(rrl->window + 1); |
1129 | 0 | set_age(rrl, e, now); |
1130 | 0 | } |
1131 | 0 | } |
1132 | 0 | UNLOCK(&rrl->lock); |
1133 | 0 | return (DNS_RRL_RESULT_OK); |
1134 | 0 | } |
1135 | | |
1136 | | /* |
1137 | | * Find the right kind of entry, creating it if necessary. |
1138 | | * If that is impossible, then nothing more can be done |
1139 | | */ |
1140 | 0 | switch (resp_result) { |
1141 | 0 | case ISC_R_SUCCESS: |
1142 | 0 | rtype = DNS_RRL_RTYPE_QUERY; |
1143 | 0 | break; |
1144 | 0 | case DNS_R_DELEGATION: |
1145 | 0 | rtype = DNS_RRL_RTYPE_REFERRAL; |
1146 | 0 | break; |
1147 | 0 | case DNS_R_NXRRSET: |
1148 | 0 | rtype = DNS_RRL_RTYPE_NODATA; |
1149 | 0 | break; |
1150 | 0 | case DNS_R_NXDOMAIN: |
1151 | 0 | rtype = DNS_RRL_RTYPE_NXDOMAIN; |
1152 | 0 | break; |
1153 | 0 | default: |
1154 | 0 | rtype = DNS_RRL_RTYPE_ERROR; |
1155 | 0 | break; |
1156 | 0 | } |
1157 | 0 | e = get_entry(rrl, client_addr, zone, qclass, qtype, qname, rtype, now, |
1158 | 0 | true, log_buf, log_buf_len); |
1159 | 0 | if (e == NULL) { |
1160 | 0 | UNLOCK(&rrl->lock); |
1161 | 0 | return (DNS_RRL_RESULT_OK); |
1162 | 0 | } |
1163 | | |
1164 | 0 | if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) { |
1165 | | /* |
1166 | | * Do not worry about speed or releasing the lock. |
1167 | | * This message appears before messages from debit_rrl_entry(). |
1168 | | */ |
1169 | 0 | make_log_buf(rrl, e, "consider limiting ", NULL, false, qname, |
1170 | 0 | false, DNS_RRL_RESULT_OK, resp_result, log_buf, |
1171 | 0 | log_buf_len); |
1172 | 0 | isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, |
1173 | 0 | DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1, "%s", |
1174 | 0 | log_buf); |
1175 | 0 | } |
1176 | |
|
1177 | 0 | rrl_result = debit_rrl_entry(rrl, e, qps, scale, client_addr, now, |
1178 | 0 | log_buf, log_buf_len); |
1179 | |
|
1180 | 0 | if (rrl->all_per_second.r != 0) { |
1181 | | /* |
1182 | | * We must debit the all-per-second token bucket if we have |
1183 | | * an all-per-second limit for the IP address. |
1184 | | * The all-per-second limit determines the log message |
1185 | | * when both limits are hit. |
1186 | | * The response limiting must continue if the |
1187 | | * all-per-second limiting lapses. |
1188 | | */ |
1189 | 0 | dns_rrl_entry_t *e_all; |
1190 | 0 | dns_rrl_result_t rrl_all_result; |
1191 | |
|
1192 | 0 | e_all = get_entry(rrl, client_addr, zone, 0, dns_rdatatype_none, |
1193 | 0 | NULL, DNS_RRL_RTYPE_ALL, now, true, log_buf, |
1194 | 0 | log_buf_len); |
1195 | 0 | if (e_all == NULL) { |
1196 | 0 | UNLOCK(&rrl->lock); |
1197 | 0 | return (DNS_RRL_RESULT_OK); |
1198 | 0 | } |
1199 | 0 | rrl_all_result = debit_rrl_entry(rrl, e_all, qps, scale, |
1200 | 0 | client_addr, now, log_buf, |
1201 | 0 | log_buf_len); |
1202 | 0 | if (rrl_all_result != DNS_RRL_RESULT_OK) { |
1203 | 0 | e = e_all; |
1204 | 0 | rrl_result = rrl_all_result; |
1205 | 0 | if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) { |
1206 | 0 | make_log_buf(rrl, e, |
1207 | 0 | "prefer all-per-second limiting ", |
1208 | 0 | NULL, true, qname, false, |
1209 | 0 | DNS_RRL_RESULT_OK, resp_result, |
1210 | 0 | log_buf, log_buf_len); |
1211 | 0 | isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, |
1212 | 0 | DNS_LOGMODULE_REQUEST, |
1213 | 0 | DNS_RRL_LOG_DEBUG1, "%s", |
1214 | 0 | log_buf); |
1215 | 0 | } |
1216 | 0 | } |
1217 | 0 | } |
1218 | | |
1219 | 0 | if (rrl_result == DNS_RRL_RESULT_OK) { |
1220 | 0 | UNLOCK(&rrl->lock); |
1221 | 0 | return (DNS_RRL_RESULT_OK); |
1222 | 0 | } |
1223 | | |
1224 | | /* |
1225 | | * Log occasionally in the rate-limit category. |
1226 | | */ |
1227 | 0 | if ((!e->logged || e->log_secs >= DNS_RRL_MAX_LOG_SECS) && |
1228 | 0 | isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP)) |
1229 | 0 | { |
1230 | 0 | make_log_buf(rrl, e, rrl->log_only ? "would " : NULL, |
1231 | 0 | e->logged ? "continue limiting " : "limit ", true, |
1232 | 0 | qname, true, DNS_RRL_RESULT_OK, resp_result, |
1233 | 0 | log_buf, log_buf_len); |
1234 | 0 | if (!e->logged) { |
1235 | 0 | e->logged = true; |
1236 | 0 | if (++rrl->num_logged <= 1) { |
1237 | 0 | rrl->last_logged = e; |
1238 | 0 | } |
1239 | 0 | } |
1240 | 0 | e->log_secs = 0; |
1241 | | |
1242 | | /* |
1243 | | * Avoid holding the lock. |
1244 | | */ |
1245 | 0 | if (!wouldlog) { |
1246 | 0 | UNLOCK(&rrl->lock); |
1247 | 0 | e = NULL; |
1248 | 0 | } |
1249 | 0 | isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL, |
1250 | 0 | DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP, "%s", |
1251 | 0 | log_buf); |
1252 | 0 | } |
1253 | | |
1254 | | /* |
1255 | | * Make a log message for the caller. |
1256 | | */ |
1257 | 0 | if (wouldlog) { |
1258 | 0 | make_log_buf(rrl, e, |
1259 | 0 | rrl->log_only ? "would rate limit " |
1260 | 0 | : "rate limit ", |
1261 | 0 | NULL, false, qname, false, rrl_result, resp_result, |
1262 | 0 | log_buf, log_buf_len); |
1263 | 0 | } |
1264 | |
|
1265 | 0 | if (e != NULL) { |
1266 | | /* |
1267 | | * Do not save the qname unless we might need it for |
1268 | | * the ending log message. |
1269 | | */ |
1270 | 0 | if (!e->logged) { |
1271 | 0 | free_qname(rrl, e); |
1272 | 0 | } |
1273 | 0 | UNLOCK(&rrl->lock); |
1274 | 0 | } |
1275 | |
|
1276 | 0 | return (rrl_result); |
1277 | 0 | } |
1278 | | |
1279 | | void |
1280 | 2 | dns_rrl_view_destroy(dns_view_t *view) { |
1281 | 2 | dns_rrl_t *rrl; |
1282 | 2 | dns_rrl_block_t *b; |
1283 | 2 | dns_rrl_hash_t *h; |
1284 | 2 | char log_buf[DNS_RRL_LOG_BUF_LEN]; |
1285 | 2 | int i; |
1286 | | |
1287 | 2 | rrl = view->rrl; |
1288 | 2 | if (rrl == NULL) { |
1289 | 2 | return; |
1290 | 2 | } |
1291 | 0 | view->rrl = NULL; |
1292 | | |
1293 | | /* |
1294 | | * Assume the caller takes care of locking the view and anything else. |
1295 | | */ |
1296 | |
|
1297 | 0 | if (rrl->num_logged > 0) { |
1298 | 0 | log_stops(rrl, 0, INT32_MAX, log_buf, sizeof(log_buf)); |
1299 | 0 | } |
1300 | |
|
1301 | 0 | for (i = 0; i < DNS_RRL_QNAMES; ++i) { |
1302 | 0 | if (rrl->qnames[i] == NULL) { |
1303 | 0 | break; |
1304 | 0 | } |
1305 | 0 | isc_mem_put(rrl->mctx, rrl->qnames[i], sizeof(*rrl->qnames[i])); |
1306 | 0 | } |
1307 | |
|
1308 | 0 | if (rrl->exempt != NULL) { |
1309 | 0 | dns_acl_detach(&rrl->exempt); |
1310 | 0 | } |
1311 | |
|
1312 | 0 | isc_mutex_destroy(&rrl->lock); |
1313 | |
|
1314 | 0 | while (!ISC_LIST_EMPTY(rrl->blocks)) { |
1315 | 0 | b = ISC_LIST_HEAD(rrl->blocks); |
1316 | 0 | ISC_LIST_UNLINK(rrl->blocks, b, link); |
1317 | 0 | isc_mem_put(rrl->mctx, b, b->size); |
1318 | 0 | } |
1319 | |
|
1320 | 0 | h = rrl->hash; |
1321 | 0 | if (h != NULL) { |
1322 | 0 | isc_mem_put(rrl->mctx, h, |
1323 | 0 | sizeof(*h) + (h->length - 1) * sizeof(h->bins[0])); |
1324 | 0 | } |
1325 | |
|
1326 | 0 | h = rrl->old_hash; |
1327 | 0 | if (h != NULL) { |
1328 | 0 | isc_mem_put(rrl->mctx, h, |
1329 | 0 | sizeof(*h) + (h->length - 1) * sizeof(h->bins[0])); |
1330 | 0 | } |
1331 | |
|
1332 | 0 | isc_mem_putanddetach(&rrl->mctx, rrl, sizeof(*rrl)); |
1333 | 0 | } |
1334 | | |
1335 | | isc_result_t |
1336 | 0 | dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries) { |
1337 | 0 | dns_rrl_t *rrl; |
1338 | 0 | isc_result_t result; |
1339 | |
|
1340 | 0 | *rrlp = NULL; |
1341 | |
|
1342 | 0 | rrl = isc_mem_getx(view->mctx, sizeof(*rrl), ISC_MEM_ZERO); |
1343 | 0 | isc_mem_attach(view->mctx, &rrl->mctx); |
1344 | 0 | isc_mutex_init(&rrl->lock); |
1345 | 0 | isc_stdtime_get(&rrl->ts_bases[0]); |
1346 | |
|
1347 | 0 | view->rrl = rrl; |
1348 | |
|
1349 | 0 | result = expand_entries(rrl, min_entries); |
1350 | 0 | if (result != ISC_R_SUCCESS) { |
1351 | 0 | dns_rrl_view_destroy(view); |
1352 | 0 | return (result); |
1353 | 0 | } |
1354 | 0 | result = expand_rrl_hash(rrl, 0); |
1355 | 0 | if (result != ISC_R_SUCCESS) { |
1356 | 0 | dns_rrl_view_destroy(view); |
1357 | 0 | return (result); |
1358 | 0 | } |
1359 | | |
1360 | 0 | *rrlp = rrl; |
1361 | 0 | return (ISC_R_SUCCESS); |
1362 | 0 | } |