xref: /netbsd-src/external/bsd/nsd/dist/rrl.c (revision 811a4a0195236f69295602fbee687a174d42af9b)
1d83a80eeSchristos 
2d83a80eeSchristos /* rrl.c - Response Rate Limiting for NSD.
3d83a80eeSchristos  * By W.C.A. Wijngaards
4d83a80eeSchristos  * Copyright 2012, NLnet Labs.
5d83a80eeSchristos  * BSD, see LICENSE.
6d83a80eeSchristos  */
7d83a80eeSchristos #include "config.h"
8d83a80eeSchristos #include <errno.h>
9d83a80eeSchristos #include "rrl.h"
10d83a80eeSchristos #include "util.h"
11d83a80eeSchristos #include "lookup3.h"
12d83a80eeSchristos #include "options.h"
13d83a80eeSchristos 
14d83a80eeSchristos #ifdef RATELIMIT
15d83a80eeSchristos 
16d83a80eeSchristos #ifdef HAVE_MMAP
17d83a80eeSchristos #include <sys/mman.h>
18d83a80eeSchristos #if defined(MAP_ANON) && !defined(MAP_ANONYMOUS)
19d83a80eeSchristos #define MAP_ANONYMOUS   MAP_ANON
20d83a80eeSchristos #endif
21d83a80eeSchristos #endif /* HAVE_MMAP */
22d83a80eeSchristos 
23d83a80eeSchristos 
24d83a80eeSchristos /**
25d83a80eeSchristos  * The rate limiting data structure bucket, this represents one rate of
26d83a80eeSchristos  * packets from a single source.
27d83a80eeSchristos  * Smoothed average rates.
28d83a80eeSchristos  */
29d83a80eeSchristos struct rrl_bucket {
30d83a80eeSchristos 	/* the source netmask */
31d83a80eeSchristos 	uint64_t source;
32d83a80eeSchristos 	/* rate, in queries per second, which due to rate=r(t)+r(t-1)/2 is
33d83a80eeSchristos 	 * equal to double the queries per second */
34d83a80eeSchristos 	uint32_t rate;
35d83a80eeSchristos 	/* the full hash */
36d83a80eeSchristos 	uint32_t hash;
37d83a80eeSchristos 	/* counter for queries arrived in this second */
38d83a80eeSchristos 	uint32_t counter;
39d83a80eeSchristos 	/* timestamp, which time is the time of the counter, the rate is from
40d83a80eeSchristos 	 * one timestep before that. */
41d83a80eeSchristos 	int32_t stamp;
42d83a80eeSchristos 	/* flags for the source mask and type */
43d83a80eeSchristos 	uint16_t flags;
44d83a80eeSchristos };
45d83a80eeSchristos 
46d83a80eeSchristos /* the (global) array of RRL buckets */
47d83a80eeSchristos static struct rrl_bucket* rrl_array = NULL;
48d83a80eeSchristos static size_t rrl_array_size = RRL_BUCKETS;
49d83a80eeSchristos static uint32_t rrl_ratelimit = RRL_LIMIT; /* 2x qps */
50d83a80eeSchristos static uint8_t rrl_slip_ratio = RRL_SLIP;
51d83a80eeSchristos static uint8_t rrl_ipv4_prefixlen = RRL_IPV4_PREFIX_LENGTH;
52d83a80eeSchristos static uint8_t rrl_ipv6_prefixlen = RRL_IPV6_PREFIX_LENGTH;
53d83a80eeSchristos static uint64_t rrl_ipv6_mask; /* max prefixlen 64 */
54d83a80eeSchristos static uint32_t rrl_whitelist_ratelimit = RRL_WLIST_LIMIT; /* 2x qps */
55d83a80eeSchristos 
56d83a80eeSchristos /* the array of mmaps for the children (saved between reloads) */
57d83a80eeSchristos static void** rrl_maps = NULL;
58d83a80eeSchristos static size_t rrl_maps_num = 0;
59d83a80eeSchristos 
rrl_mmap_init(int numch,size_t numbuck,size_t lm,size_t wlm,size_t sm,size_t plf,size_t pls)60d83a80eeSchristos void rrl_mmap_init(int numch, size_t numbuck, size_t lm, size_t wlm, size_t sm,
61d83a80eeSchristos 	size_t plf, size_t pls)
62d83a80eeSchristos {
63d83a80eeSchristos #ifdef HAVE_MMAP
64d83a80eeSchristos 	size_t i;
65d83a80eeSchristos #endif
66d83a80eeSchristos 	if(numbuck != 0)
67d83a80eeSchristos 		rrl_array_size = numbuck;
68d83a80eeSchristos 	rrl_ratelimit = lm*2;
69d83a80eeSchristos 	rrl_slip_ratio = sm;
70d83a80eeSchristos 	rrl_ipv4_prefixlen = plf;
71d83a80eeSchristos 	rrl_ipv6_prefixlen = pls;
72d83a80eeSchristos 	if (pls <= 32) {
73d83a80eeSchristos 		rrl_ipv6_mask = ((uint64_t) htonl(0xffffffff << (32-pls))) << 32;
74d83a80eeSchristos 	} else {
75d83a80eeSchristos 		rrl_ipv6_mask =  ((uint64_t) htonl(0xffffffff << (64-pls))) |
76d83a80eeSchristos 			(((uint64_t)0xffffffff)<<32);
77d83a80eeSchristos 	}
78d83a80eeSchristos 	rrl_whitelist_ratelimit = wlm*2;
79d83a80eeSchristos #ifdef HAVE_MMAP
80d83a80eeSchristos 	/* allocate the ratelimit hashtable in a memory map so it is
81d83a80eeSchristos 	 * preserved across reforks (every child its own table) */
82d83a80eeSchristos 	rrl_maps_num = (size_t)numch;
83d83a80eeSchristos 	rrl_maps = (void**)xmallocarray(rrl_maps_num, sizeof(void*));
84d83a80eeSchristos 	for(i=0; i<rrl_maps_num; i++) {
85d83a80eeSchristos 		rrl_maps[i] = mmap(NULL,
86d83a80eeSchristos 			sizeof(struct rrl_bucket)*rrl_array_size,
87d83a80eeSchristos 			PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
88d83a80eeSchristos 		if(rrl_maps[i] == MAP_FAILED) {
89d83a80eeSchristos 			log_msg(LOG_ERR, "rrl: mmap failed: %s",
90d83a80eeSchristos 				strerror(errno));
91d83a80eeSchristos 			exit(1);
92d83a80eeSchristos 		}
93d83a80eeSchristos 		memset(rrl_maps[i], 0,
94d83a80eeSchristos 			sizeof(struct rrl_bucket)*rrl_array_size);
95d83a80eeSchristos 	}
96d83a80eeSchristos #else
97d83a80eeSchristos 	(void)numch;
98d83a80eeSchristos 	rrl_maps_num = 0;
99d83a80eeSchristos 	rrl_maps = NULL;
100d83a80eeSchristos #endif
101d83a80eeSchristos }
102d83a80eeSchristos 
rrl_mmap_deinit(void)103da4c7d9dSchristos void rrl_mmap_deinit(void)
104da4c7d9dSchristos {
105da4c7d9dSchristos #ifdef HAVE_MMAP
106da4c7d9dSchristos 	size_t i;
107da4c7d9dSchristos 	for(i=0; i<rrl_maps_num; i++) {
108da4c7d9dSchristos 		munmap(rrl_maps[i], sizeof(struct rrl_bucket)*rrl_array_size);
109da4c7d9dSchristos 		rrl_maps[i] = NULL;
110da4c7d9dSchristos 	}
111da4c7d9dSchristos 	free(rrl_maps);
112da4c7d9dSchristos 	rrl_maps = NULL;
113da4c7d9dSchristos #endif
114da4c7d9dSchristos }
115da4c7d9dSchristos 
rrl_mmap_deinit_keep_mmap(void)116da4c7d9dSchristos void rrl_mmap_deinit_keep_mmap(void)
117da4c7d9dSchristos {
118da4c7d9dSchristos #ifdef HAVE_MMAP
119da4c7d9dSchristos 	free(rrl_maps);
120da4c7d9dSchristos 	rrl_maps = NULL;
121da4c7d9dSchristos #endif
122da4c7d9dSchristos }
123da4c7d9dSchristos 
rrl_set_limit(size_t lm,size_t wlm,size_t sm)124d83a80eeSchristos void rrl_set_limit(size_t lm, size_t wlm, size_t sm)
125d83a80eeSchristos {
126d83a80eeSchristos 	rrl_ratelimit = lm*2;
127d83a80eeSchristos 	rrl_whitelist_ratelimit = wlm*2;
128d83a80eeSchristos 	rrl_slip_ratio = sm;
129d83a80eeSchristos }
130d83a80eeSchristos 
rrl_init(size_t ch)131d83a80eeSchristos void rrl_init(size_t ch)
132d83a80eeSchristos {
133d83a80eeSchristos 	if(!rrl_maps || ch >= rrl_maps_num)
134d83a80eeSchristos 	    rrl_array = xalloc_array_zero(sizeof(struct rrl_bucket),
135d83a80eeSchristos 	    	rrl_array_size);
136d83a80eeSchristos #ifdef HAVE_MMAP
137d83a80eeSchristos 	else rrl_array = (struct rrl_bucket*)rrl_maps[ch];
138d83a80eeSchristos #endif
139d83a80eeSchristos }
140d83a80eeSchristos 
rrl_deinit(size_t ch)141da4c7d9dSchristos void rrl_deinit(size_t ch)
142da4c7d9dSchristos {
143da4c7d9dSchristos 	if(!rrl_maps || ch >= rrl_maps_num)
144da4c7d9dSchristos 		free(rrl_array);
145da4c7d9dSchristos 	rrl_array = NULL;
146da4c7d9dSchristos }
147da4c7d9dSchristos 
148d83a80eeSchristos /** return the source netblock of the query, this is the genuine source
149d83a80eeSchristos  * for genuine queries and the target for reflected packets */
rrl_get_source(query_type * query,uint16_t * c2)150d83a80eeSchristos static uint64_t rrl_get_source(query_type* query, uint16_t* c2)
151d83a80eeSchristos {
152d83a80eeSchristos 	/* note there is an IPv6 subnet, that maps
153d83a80eeSchristos 	 * to the same buckets as IPv4 space, but there is a flag in c2
154d83a80eeSchristos 	 * that makes the hash different */
155d83a80eeSchristos #ifdef INET6
156*811a4a01Schristos 	if( ((struct sockaddr_in*)&query->client_addr)->sin_family == AF_INET) {
157d83a80eeSchristos 		*c2 = 0;
158*811a4a01Schristos 		return ((struct sockaddr_in*)&query->client_addr)->
159d83a80eeSchristos 			sin_addr.s_addr & htonl(0xffffffff << (32-rrl_ipv4_prefixlen));
160d83a80eeSchristos 	} else {
161d83a80eeSchristos 		uint64_t s;
162d83a80eeSchristos 		*c2 = rrl_ip6;
163*811a4a01Schristos 		memmove(&s, &((struct sockaddr_in6*)&query->client_addr)->sin6_addr,
164d83a80eeSchristos 			sizeof(s));
165d83a80eeSchristos 		return s & rrl_ipv6_mask;
166d83a80eeSchristos 	}
167d83a80eeSchristos #else
168d83a80eeSchristos 	*c2 = 0;
169*811a4a01Schristos 	return query->client_addr.sin_addr.s_addr & htonl(0xffffffff << (32-rrl_ipv4_prefixlen));
170d83a80eeSchristos #endif
171d83a80eeSchristos }
172d83a80eeSchristos 
173d83a80eeSchristos /** debug source to string */
rrlsource2str(uint64_t s,uint16_t c2)174d83a80eeSchristos static const char* rrlsource2str(uint64_t s, uint16_t c2)
175d83a80eeSchristos {
176d83a80eeSchristos 	static char buf[64];
177d83a80eeSchristos 	struct in_addr a4;
178d83a80eeSchristos #ifdef INET6
179d83a80eeSchristos 	if(c2) {
180d83a80eeSchristos 		/* IPv6 */
181d83a80eeSchristos 		struct in6_addr a6;
182d83a80eeSchristos 		memset(&a6, 0, sizeof(a6));
183d83a80eeSchristos 		memmove(&a6, &s, sizeof(s));
184d83a80eeSchristos 		if(!inet_ntop(AF_INET6, &a6, buf, sizeof(buf)))
185d83a80eeSchristos 			strlcpy(buf, "[ip6 ntop failed]", sizeof(buf));
186d83a80eeSchristos 		else {
1873fb62404Schristos 			static char prefix[5];
188d83a80eeSchristos 			snprintf(prefix, sizeof(prefix), "/%d", rrl_ipv6_prefixlen);
189d83a80eeSchristos 			strlcat(buf, &prefix[0], sizeof(buf));
190d83a80eeSchristos 		}
191d83a80eeSchristos 		return buf;
192d83a80eeSchristos 	}
193d83a80eeSchristos #else
194d83a80eeSchristos 	(void)c2;
195d83a80eeSchristos #endif
196d83a80eeSchristos 	/* ipv4 */
197d83a80eeSchristos 	a4.s_addr = (uint32_t)s;
198d83a80eeSchristos 	if(!inet_ntop(AF_INET, &a4, buf, sizeof(buf)))
199d83a80eeSchristos 		strlcpy(buf, "[ip4 ntop failed]", sizeof(buf));
200d83a80eeSchristos 	else {
2013fb62404Schristos 		static char prefix[5];
202d83a80eeSchristos 		snprintf(prefix, sizeof(prefix), "/%d", rrl_ipv4_prefixlen);
203d83a80eeSchristos 		strlcat(buf, &prefix[0], sizeof(buf));
204d83a80eeSchristos 	}
205d83a80eeSchristos 	return buf;
206d83a80eeSchristos }
207d83a80eeSchristos 
rrlstr2type(const char * s)208d83a80eeSchristos enum rrl_type rrlstr2type(const char* s)
209d83a80eeSchristos {
210d83a80eeSchristos 	if(strcmp(s, "nxdomain")==0) return rrl_type_nxdomain;
211d83a80eeSchristos 	else if(strcmp(s, "error")==0) return rrl_type_error;
212d83a80eeSchristos 	else if(strcmp(s, "referral")==0) return rrl_type_referral;
213d83a80eeSchristos 	else if(strcmp(s, "any")==0) return rrl_type_any;
214d83a80eeSchristos 	else if(strcmp(s, "wildcard")==0) return rrl_type_wildcard;
215d83a80eeSchristos 	else if(strcmp(s, "nodata")==0) return rrl_type_nodata;
216d83a80eeSchristos 	else if(strcmp(s, "dnskey")==0) return rrl_type_dnskey;
217d83a80eeSchristos 	else if(strcmp(s, "positive")==0) return rrl_type_positive;
218d83a80eeSchristos 	else if(strcmp(s, "rrsig")==0) return rrl_type_rrsig;
219d83a80eeSchristos 	else if(strcmp(s, "all")==0) return rrl_type_all;
220d83a80eeSchristos 	return 0; /* unknown */
221d83a80eeSchristos }
222d83a80eeSchristos 
rrltype2str(enum rrl_type c)223d83a80eeSchristos const char* rrltype2str(enum rrl_type c)
224d83a80eeSchristos {
225d83a80eeSchristos 	switch(c & 0x0fff) {
226d83a80eeSchristos 		case rrl_type_nxdomain: return "nxdomain";
227d83a80eeSchristos 		case rrl_type_error: return "error";
228d83a80eeSchristos 		case rrl_type_referral: return "referral";
229d83a80eeSchristos 		case rrl_type_any: return "any";
230d83a80eeSchristos 		case rrl_type_wildcard: return "wildcard";
231d83a80eeSchristos 		case rrl_type_nodata: return "nodata";
232d83a80eeSchristos 		case rrl_type_dnskey: return "dnskey";
233d83a80eeSchristos 		case rrl_type_positive: return "positive";
234d83a80eeSchristos 		case rrl_type_rrsig: return "rrsig";
235d83a80eeSchristos 		case rrl_type_all: return "all";
236d83a80eeSchristos 	}
237d83a80eeSchristos 	return "unknown";
238d83a80eeSchristos }
239d83a80eeSchristos 
240d83a80eeSchristos /** classify the query in a number of different types, each has separate
241d83a80eeSchristos  * ratelimiting, so that positive queries are not impeded by others */
rrl_classify(query_type * query,const uint8_t ** d,size_t * d_len)242d83a80eeSchristos static uint16_t rrl_classify(query_type* query, const uint8_t** d,
243d83a80eeSchristos 	size_t* d_len)
244d83a80eeSchristos {
245d83a80eeSchristos 	if(RCODE(query->packet) == RCODE_NXDOMAIN) {
246d83a80eeSchristos 		if(query->zone && query->zone->apex) {
247d83a80eeSchristos 			*d = dname_name(domain_dname(query->zone->apex));
248d83a80eeSchristos 			*d_len = domain_dname(query->zone->apex)->name_size;
249d83a80eeSchristos 		}
250d83a80eeSchristos 		return rrl_type_nxdomain;
251d83a80eeSchristos 	}
252d83a80eeSchristos 	if(RCODE(query->packet) != RCODE_OK) {
253d83a80eeSchristos 		if(query->zone && query->zone->apex) {
254d83a80eeSchristos 			*d = dname_name(domain_dname(query->zone->apex));
255d83a80eeSchristos 			*d_len = domain_dname(query->zone->apex)->name_size;
256d83a80eeSchristos 		}
257d83a80eeSchristos 		return rrl_type_error;
258d83a80eeSchristos 	}
259d83a80eeSchristos 	if(query->delegation_domain) {
260d83a80eeSchristos 		*d = dname_name(domain_dname(query->delegation_domain));
261d83a80eeSchristos 		*d_len = domain_dname(query->delegation_domain)->name_size;
262d83a80eeSchristos 		return rrl_type_referral;
263d83a80eeSchristos 	}
264d83a80eeSchristos 	if(query->qtype == TYPE_ANY) {
265d83a80eeSchristos 		if(query->qname) {
266d83a80eeSchristos 			*d = dname_name(query->qname);
267d83a80eeSchristos 			*d_len = query->qname->name_size;
268d83a80eeSchristos 		}
269d83a80eeSchristos 		return rrl_type_any;
270d83a80eeSchristos 	}
271d83a80eeSchristos 	if(query->qtype == TYPE_RRSIG) {
272d83a80eeSchristos 		if(query->qname) {
273d83a80eeSchristos 			*d = dname_name(query->qname);
274d83a80eeSchristos 			*d_len = query->qname->name_size;
275d83a80eeSchristos 		}
276d83a80eeSchristos 		return rrl_type_rrsig;
277d83a80eeSchristos 	}
278d83a80eeSchristos 	if(query->wildcard_domain) {
279d83a80eeSchristos 		*d = dname_name(domain_dname(query->wildcard_domain));
280d83a80eeSchristos 		*d_len = domain_dname(query->wildcard_domain)->name_size;
281d83a80eeSchristos 		return rrl_type_wildcard;
282d83a80eeSchristos 	}
283d83a80eeSchristos 	if(ANCOUNT(query->packet) == 0) {
284d83a80eeSchristos 		if(query->zone && query->zone->apex) {
285d83a80eeSchristos 			*d = dname_name(domain_dname(query->zone->apex));
286d83a80eeSchristos 			*d_len = domain_dname(query->zone->apex)->name_size;
287d83a80eeSchristos 		}
288d83a80eeSchristos 		return rrl_type_nodata;
289d83a80eeSchristos 	}
290d83a80eeSchristos 	if(query->qtype == TYPE_DNSKEY) {
291d83a80eeSchristos 		if(query->qname) {
292d83a80eeSchristos 			*d = dname_name(query->qname);
293d83a80eeSchristos 			*d_len = query->qname->name_size;
294d83a80eeSchristos 		}
295d83a80eeSchristos 		return rrl_type_dnskey;
296d83a80eeSchristos 	}
297d83a80eeSchristos 	/* positive */
298d83a80eeSchristos 	if(query->qname) {
299d83a80eeSchristos 		*d = dname_name(query->qname);
300d83a80eeSchristos 		*d_len = query->qname->name_size;
301d83a80eeSchristos 	}
302d83a80eeSchristos 	return rrl_type_positive;
303d83a80eeSchristos }
304d83a80eeSchristos 
305d83a80eeSchristos /** Examine the query and return hash and source of netblock. */
examine_query(query_type * query,uint32_t * hash,uint64_t * source,uint16_t * flags,uint32_t * lm)306d83a80eeSchristos static void examine_query(query_type* query, uint32_t* hash, uint64_t* source,
307d83a80eeSchristos 	uint16_t* flags, uint32_t* lm)
308d83a80eeSchristos {
309d83a80eeSchristos 	/* compile a binary string representing the query */
310d83a80eeSchristos 	uint16_t c, c2;
311d83a80eeSchristos 	/* size with 16 bytes to spare */
312d83a80eeSchristos 	uint8_t buf[MAXDOMAINLEN + sizeof(*source) + sizeof(c) + 16];
313d83a80eeSchristos 	const uint8_t* dname = NULL; size_t dname_len = 0;
314d83a80eeSchristos 	uint32_t r = 0x267fcd16;
315d83a80eeSchristos 
316d83a80eeSchristos 	*source = rrl_get_source(query, &c2);
317d83a80eeSchristos 	c = rrl_classify(query, &dname, &dname_len);
318d83a80eeSchristos 	if(query->zone && query->zone->opts &&
319d83a80eeSchristos 		(query->zone->opts->pattern->rrl_whitelist & c))
320d83a80eeSchristos 		*lm = rrl_whitelist_ratelimit;
321d83a80eeSchristos 	if(*lm == 0) return;
322d83a80eeSchristos 	c |= c2;
323d83a80eeSchristos 	*flags = c;
324d83a80eeSchristos 	memmove(buf, source, sizeof(*source));
325d83a80eeSchristos 	memmove(buf+sizeof(*source), &c, sizeof(c));
326d83a80eeSchristos 
327d83a80eeSchristos 	DEBUG(DEBUG_QUERY, 1, (LOG_INFO, "rrl_examine type %s name %s", rrltype2str(c), dname?wiredname2str(dname):"NULL"));
328d83a80eeSchristos 
329d83a80eeSchristos 	/* and hash it */
330d83a80eeSchristos 	if(dname && dname_len <= MAXDOMAINLEN) {
331d83a80eeSchristos 		memmove(buf+sizeof(*source)+sizeof(c), dname, dname_len);
332d83a80eeSchristos 		*hash = hashlittle(buf, sizeof(*source)+sizeof(c)+dname_len, r);
333d83a80eeSchristos 	} else
334d83a80eeSchristos 		*hash = hashlittle(buf, sizeof(*source)+sizeof(c), r);
335d83a80eeSchristos }
336d83a80eeSchristos 
337d83a80eeSchristos /* age the bucket because elapsed time steps have gone by */
rrl_attenuate_bucket(struct rrl_bucket * b,int32_t elapsed)338d83a80eeSchristos static void rrl_attenuate_bucket(struct rrl_bucket* b, int32_t elapsed)
339d83a80eeSchristos {
340d83a80eeSchristos 	if(elapsed > 16) {
341d83a80eeSchristos 		b->rate = 0;
342d83a80eeSchristos 	} else {
343d83a80eeSchristos 		/* divide rate /2 for every elapsed time step, because
344d83a80eeSchristos 		 * the counters in the inbetween steps were 0 */
345d83a80eeSchristos 		/* r(t) = 0 + 0/2 + 0/4 + .. + oldrate/2^dt */
346d83a80eeSchristos 		b->rate >>= elapsed;
347d83a80eeSchristos 		/* we know that elapsed >= 2 */
348d83a80eeSchristos 		b->rate += (b->counter>>(elapsed-1));
349d83a80eeSchristos 	}
350d83a80eeSchristos }
351d83a80eeSchristos 
352d83a80eeSchristos /** log a message about ratelimits */
353d83a80eeSchristos static void
rrl_msg(query_type * query,const char * str)354d83a80eeSchristos rrl_msg(query_type* query, const char* str)
355d83a80eeSchristos {
356d83a80eeSchristos 	uint16_t c, c2, wl = 0;
357d83a80eeSchristos 	const uint8_t* d = NULL;
358d83a80eeSchristos 	size_t d_len;
359d83a80eeSchristos 	uint64_t s;
360d83a80eeSchristos 	char address[128];
361d83a80eeSchristos 	if(verbosity < 1) return;
362*811a4a01Schristos 	addr2str(&query->client_addr, address, sizeof(address));
363d83a80eeSchristos 	s = rrl_get_source(query, &c2);
364d83a80eeSchristos 	c = rrl_classify(query, &d, &d_len) | c2;
365d83a80eeSchristos 	if(query->zone && query->zone->opts &&
366d83a80eeSchristos 		(query->zone->opts->pattern->rrl_whitelist & c))
367d83a80eeSchristos 		wl = 1;
368d83a80eeSchristos 	log_msg(LOG_INFO, "ratelimit %s %s type %s%s target %s query %s %s",
369d83a80eeSchristos 		str, d?wiredname2str(d):"", rrltype2str(c),
370d83a80eeSchristos 		wl?"(whitelisted)":"", rrlsource2str(s, c2),
371d83a80eeSchristos 		address, rrtype_to_string(query->qtype));
372d83a80eeSchristos }
373d83a80eeSchristos 
374d83a80eeSchristos /** true if the query used to be blocked by the ratelimit */
375d83a80eeSchristos static int
used_to_block(uint32_t rate,uint32_t counter,uint32_t lm)376d83a80eeSchristos used_to_block(uint32_t rate, uint32_t counter, uint32_t lm)
377d83a80eeSchristos {
378d83a80eeSchristos 	return rate >= lm || counter+rate/2 >= lm;
379d83a80eeSchristos }
380d83a80eeSchristos 
381d83a80eeSchristos /** update the rate in a ratelimit bucket, return actual rate */
rrl_update(query_type * query,uint32_t hash,uint64_t source,uint16_t flags,int32_t now,uint32_t lm)382d83a80eeSchristos uint32_t rrl_update(query_type* query, uint32_t hash, uint64_t source,
383d83a80eeSchristos 	uint16_t flags, int32_t now, uint32_t lm)
384d83a80eeSchristos {
385d83a80eeSchristos 	struct rrl_bucket* b = &rrl_array[hash % rrl_array_size];
386d83a80eeSchristos 
387d83a80eeSchristos 	DEBUG(DEBUG_QUERY, 1, (LOG_INFO, "source %llx hash %x oldrate %d oldcount %d stamp %d",
388d83a80eeSchristos 		(long long unsigned)source, hash, b->rate, b->counter, b->stamp));
389d83a80eeSchristos 
390d83a80eeSchristos 	/* check if different source */
391d83a80eeSchristos 	if(b->source != source || b->flags != flags || b->hash != hash) {
392d83a80eeSchristos 		/* initialise */
393d83a80eeSchristos 		/* potentially the wrong limit here, used lower nonwhitelim */
394d83a80eeSchristos 		if(verbosity >= 1 &&
395d83a80eeSchristos 			used_to_block(b->rate, b->counter, rrl_ratelimit)) {
396d83a80eeSchristos 			char address[128];
397*811a4a01Schristos 			addr2str(&query->client_addr, address, sizeof(address));
398d83a80eeSchristos 			log_msg(LOG_INFO, "ratelimit unblock ~ type %s target %s query %s %s (%s collision)",
399d83a80eeSchristos 				rrltype2str(b->flags),
400d83a80eeSchristos 				rrlsource2str(b->source, b->flags),
401d83a80eeSchristos 				address, rrtype_to_string(query->qtype),
402d83a80eeSchristos 				(b->hash!=hash?"bucket":"hash"));
403d83a80eeSchristos 		}
404d83a80eeSchristos 		b->hash = hash;
405d83a80eeSchristos 		b->source = source;
406d83a80eeSchristos 		b->flags = flags;
407d83a80eeSchristos 		b->counter = 1;
408d83a80eeSchristos 		b->rate = 0;
409d83a80eeSchristos 		b->stamp = now;
410d83a80eeSchristos 		return 1;
411d83a80eeSchristos 	}
412d83a80eeSchristos 	/* this is the same source */
413d83a80eeSchristos 
414d83a80eeSchristos 	/* check if old, zero or smooth it */
415d83a80eeSchristos 	/* circular arith for time */
416d83a80eeSchristos 	if(now - b->stamp == 1) {
417d83a80eeSchristos 		/* very busy bucket and time just stepped one step */
418d83a80eeSchristos 		int oldblock = used_to_block(b->rate, b->counter, lm);
419d83a80eeSchristos 		b->rate = b->rate/2 + b->counter;
420d83a80eeSchristos 		if(oldblock && b->rate < lm)
421d83a80eeSchristos 			rrl_msg(query, "unblock");
422d83a80eeSchristos 		b->counter = 1;
423d83a80eeSchristos 		b->stamp = now;
424d83a80eeSchristos 	} else if(now - b->stamp > 0) {
425d83a80eeSchristos 		/* older bucket */
426d83a80eeSchristos 		int olderblock = used_to_block(b->rate, b->counter, lm);
427d83a80eeSchristos 		rrl_attenuate_bucket(b, now - b->stamp);
428d83a80eeSchristos 		if(olderblock && b->rate < lm)
429d83a80eeSchristos 			rrl_msg(query, "unblock");
430d83a80eeSchristos 		b->counter = 1;
431d83a80eeSchristos 		b->stamp = now;
432d83a80eeSchristos 	} else if(now != b->stamp) {
433d83a80eeSchristos 		/* robust, timestamp from the future */
434d83a80eeSchristos 		if(used_to_block(b->rate, b->counter, lm))
435d83a80eeSchristos 			rrl_msg(query, "unblock");
436d83a80eeSchristos 		b->rate = 0;
437d83a80eeSchristos 		b->counter = 1;
438d83a80eeSchristos 		b->stamp = now;
439d83a80eeSchristos 	} else {
440d83a80eeSchristos 		/* bucket is from the current timestep, update counter */
441d83a80eeSchristos 		b->counter ++;
442d83a80eeSchristos 
443d83a80eeSchristos 		/* log what is blocked for operational debugging */
444d83a80eeSchristos 		if(b->counter + b->rate/2 == lm && b->rate < lm)
445d83a80eeSchristos 			rrl_msg(query, "block");
446d83a80eeSchristos 	}
447d83a80eeSchristos 
448d83a80eeSchristos 	/* return max from current rate and projected next-value for rate */
449d83a80eeSchristos 	/* so that if the rate increases suddenly very high, it is
450d83a80eeSchristos 	 * stopped halfway into the time step */
451d83a80eeSchristos 	if(b->counter > b->rate/2)
452d83a80eeSchristos 		return b->counter + b->rate/2;
453d83a80eeSchristos 	return b->rate;
454d83a80eeSchristos }
455d83a80eeSchristos 
rrl_process_query(query_type * query)456d83a80eeSchristos int rrl_process_query(query_type* query)
457d83a80eeSchristos {
458d83a80eeSchristos 	uint64_t source;
459d83a80eeSchristos 	uint32_t hash;
460d83a80eeSchristos 	/* we can use circular arithmetic here, so int32 works after 2038 */
461d83a80eeSchristos 	int32_t now = (int32_t)time(NULL);
462d83a80eeSchristos 	uint32_t lm = rrl_ratelimit;
463d83a80eeSchristos 	uint16_t flags;
464d83a80eeSchristos 	if(rrl_ratelimit == 0 && rrl_whitelist_ratelimit == 0)
465d83a80eeSchristos 		return 0;
466d83a80eeSchristos 
467d83a80eeSchristos 	/* examine query */
468d83a80eeSchristos 	examine_query(query, &hash, &source, &flags, &lm);
469d83a80eeSchristos 
470d83a80eeSchristos 	if(lm == 0)
471d83a80eeSchristos 		return 0; /* no limit for this */
472d83a80eeSchristos 
473d83a80eeSchristos 	/* update rate */
474d83a80eeSchristos 	return (rrl_update(query, hash, source, flags, now, lm) >= lm);
475d83a80eeSchristos }
476d83a80eeSchristos 
rrl_slip(query_type * query)477d83a80eeSchristos query_state_type rrl_slip(query_type* query)
478d83a80eeSchristos {
479d83a80eeSchristos 	/* discard number the packets, randomly */
480d83a80eeSchristos #ifdef HAVE_ARC4RANDOM_UNIFORM
481d83a80eeSchristos 	if((rrl_slip_ratio > 0) && ((rrl_slip_ratio == 1) || ((arc4random_uniform(rrl_slip_ratio)) == 0))) {
482d83a80eeSchristos #elif HAVE_ARC4RANDOM
483d83a80eeSchristos 	if((rrl_slip_ratio > 0) && ((rrl_slip_ratio == 1) || ((arc4random() % rrl_slip_ratio) == 0))) {
484d83a80eeSchristos #else
485d83a80eeSchristos 	if((rrl_slip_ratio > 0) && ((rrl_slip_ratio == 1) || ((random() % rrl_slip_ratio) == 0))) {
486d83a80eeSchristos #endif
487d83a80eeSchristos 		/* set TC on the rest */
488d83a80eeSchristos 		TC_SET(query->packet);
489d83a80eeSchristos 		ANCOUNT_SET(query->packet, 0);
490d83a80eeSchristos 		NSCOUNT_SET(query->packet, 0);
491d83a80eeSchristos 		ARCOUNT_SET(query->packet, 0);
492d83a80eeSchristos 		if(query->qname)
493d83a80eeSchristos 			/* header, type, class, qname */
494d83a80eeSchristos 			buffer_set_position(query->packet,
495d83a80eeSchristos 				QHEADERSZ+4+query->qname->name_size);
496d83a80eeSchristos 		else 	buffer_set_position(query->packet, QHEADERSZ);
497d83a80eeSchristos 		return QUERY_PROCESSED;
498d83a80eeSchristos 	}
499d83a80eeSchristos 	return QUERY_DISCARDED;
500d83a80eeSchristos }
501d83a80eeSchristos 
502d83a80eeSchristos #endif /* RATELIMIT */
503