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