Tomas Hozza df0cae
diff -r -u bin/named/client.c-orig bin/named/client.c
Tomas Hozza df0cae
--- bin/named/client.c-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/named/client.c	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -994,6 +994,11 @@
Adam Tkac 05cf27
 	}
Adam Tkac 05cf27
 	if (result != ISC_R_SUCCESS)
Adam Tkac 05cf27
 		goto done;
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Stop after the question if TC was set for rate limiting.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if ((client->message->flags & DNS_MESSAGEFLAG_TC) != 0)
Adam Tkac 05cf27
+		goto renderend;
Adam Tkac 05cf27
 	result = dns_message_rendersection(client->message,
Adam Tkac 05cf27
 					   DNS_SECTION_ANSWER,
Adam Tkac 05cf27
 					   DNS_MESSAGERENDER_PARTIAL |
Tomas Hozza df0cae
@@ -1134,6 +1139,51 @@
Adam Tkac 05cf27
 #endif
Adam Tkac 05cf27
 
Adam Tkac 05cf27
 	/*
Adam Tkac 05cf27
+	 * Try to rate limit error responses.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (client->view != NULL && client->view->rrl != NULL) {
Adam Tkac 05cf27
+		isc_boolean_t wouldlog;
Adam Tkac 05cf27
+		char log_buf[DNS_RRL_LOG_BUF_LEN];
Adam Tkac 05cf27
+		dns_rrl_result_t rrl_result;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+		INSIST(rcode != dns_rcode_noerror &&
Adam Tkac 05cf27
+		       rcode != dns_rcode_nxdomain);
Tomas Hozza df0cae
+		wouldlog = isc_log_wouldlog(ns_g_lctx, DNS_RRL_LOG_DROP);
Adam Tkac 05cf27
+		rrl_result = dns_rrl(client->view, &client->peeraddr,
Adam Tkac 05cf27
+				     TCP_CLIENT(client),
Adam Tkac 05cf27
+				     dns_rdataclass_in, dns_rdatatype_none,
Adam Tkac 05cf27
+				     NULL, result, client->now,
Adam Tkac 05cf27
+				     wouldlog, log_buf, sizeof(log_buf));
Adam Tkac 05cf27
+		if (rrl_result != DNS_RRL_RESULT_OK) {
Adam Tkac 05cf27
+			/*
Adam Tkac 05cf27
+			 * Log dropped errors in the query category
Adam Tkac 05cf27
+			 * so that they are not lost in silence.
Adam Tkac 05cf27
+			 * Starts of rate-limited bursts are logged in
Adam Tkac 05cf27
+			 * NS_LOGCATEGORY_RRL.
Adam Tkac 05cf27
+			 */
Adam Tkac 05cf27
+			if (wouldlog) {
Tomas Hozza df0cae
+				ns_client_log(client,
Tomas Hozza df0cae
+					      NS_LOGCATEGORY_QUERY_EERRORS,
Adam Tkac 05cf27
+					      NS_LOGMODULE_CLIENT,
Adam Tkac 05cf27
+					      DNS_RRL_LOG_DROP,
Adam Tkac 05cf27
+					      "%s", log_buf);
Adam Tkac 05cf27
+			}
Adam Tkac 05cf27
+			/*
Adam Tkac 05cf27
+			 * Some error responses cannot be 'slipped',
Tomas Hozza df0cae
+			 * so don't try to slip any error responses.
Adam Tkac 05cf27
+			 */
Adam Tkac 05cf27
+			if (!client->view->rrl->log_only) {
Tomas Hozza df0cae
+				isc_stats_increment(ns_g_server->nsstats,
Tomas Hozza df0cae
+						dns_nsstatscounter_ratedropped);
Tomas Hozza df0cae
+				isc_stats_increment(ns_g_server->nsstats,
Tomas Hozza df0cae
+						dns_nsstatscounter_dropped);
Adam Tkac 05cf27
+				ns_client_next(client, DNS_R_DROP);
Adam Tkac 05cf27
+				return;
Adam Tkac 05cf27
+			}
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
 	 * Message may be an in-progress reply that we had trouble
Adam Tkac 05cf27
 	 * with, in which case QR will be set.  We need to clear QR before
Adam Tkac 05cf27
 	 * calling dns_message_reply() to avoid triggering an assertion.
Tomas Hozza df0cae
diff -r -u bin/named/config.c-orig bin/named/config.c
Tomas Hozza df0cae
--- bin/named/config.c-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/named/config.c	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -228,6 +228,13 @@
Adam Tkac 05cf27
 	notify no;\n\
Adam Tkac 05cf27
 	allow-new-zones no;\n\
Adam Tkac 05cf27
 \n\
Adam Tkac 05cf27
+	# Prevent use of this zone in DNS amplified reflection DoS attacks\n\
Adam Tkac 05cf27
+	rate-limit {\n\
Adam Tkac 05cf27
+		responses-per-second 3;\n\
Adam Tkac 05cf27
+		slip 0;\n\
Adam Tkac 05cf27
+		min-table-size 10;\n\
Adam Tkac 05cf27
+	};\n\
Adam Tkac 05cf27
+\n\
Adam Tkac 05cf27
 	zone \"version.bind\" chaos {\n\
Adam Tkac 05cf27
 		type master;\n\
Adam Tkac 05cf27
 		database \"_builtin version\";\n\
Tomas Hozza df0cae
diff -r -u bin/named/include/named/query.h-orig bin/named/include/named/query.h
Tomas Hozza df0cae
--- bin/named/include/named/query.h-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/named/include/named/query.h	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -85,6 +85,7 @@
Adam Tkac 05cf27
 #define NS_QUERYATTR_CACHEACLOK		0x2000
Adam Tkac 05cf27
 #define NS_QUERYATTR_DNS64		0x4000
Adam Tkac 05cf27
 #define NS_QUERYATTR_DNS64EXCLUDE	0x8000
Adam Tkac 05cf27
+#define NS_QUERYATTR_RRL_CHECKED	0x10000
Adam Tkac 05cf27
 
Adam Tkac 05cf27
 
Adam Tkac 05cf27
 isc_result_t
Tomas Hozza df0cae
diff -r -u bin/named/include/named/server.h-orig bin/named/include/named/server.h
Tomas Hozza df0cae
--- bin/named/include/named/server.h-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/named/include/named/server.h	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -167,7 +167,10 @@
Adam Tkac 05cf27
 
Adam Tkac c9b941
 	dns_nsstatscounter_rpz_rewrites = 36,
Adam Tkac c9b941
 
Adam Tkac c9b941
-	dns_nsstatscounter_max = 37
Adam Tkac c9b941
+	dns_nsstatscounter_ratedropped = 37,
Adam Tkac c9b941
+	dns_nsstatscounter_rateslipped = 38,
Adam Tkac 05cf27
+
Adam Tkac c9b941
+	dns_nsstatscounter_max = 39
Adam Tkac 05cf27
 };
Adam Tkac 05cf27
 
Adam Tkac 05cf27
 void
Tomas Hozza df0cae
diff -r -u bin/named/query.c-orig bin/named/query.c
Tomas Hozza df0cae
--- bin/named/query.c-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/named/query.c	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -193,7 +193,7 @@
Tomas Hozza df0cae
 #ifdef NEWSTATS
Tomas Hozza df0cae
 	/* Do query type statistics
Tomas Hozza df0cae
 	 *
Tomas Hozza df0cae
-	 * We only increment per-type if we're using the authoriative
Tomas Hozza df0cae
+	 * We only increment per-type if we're using the authoritative
Tomas Hozza df0cae
 	 * answer counter, preventing double-counting.
Tomas Hozza df0cae
 	 */
Tomas Hozza df0cae
 	if (counter == dns_nsstatscounter_authans) {
Tomas Hozza df0cae
@@ -5865,6 +5865,128 @@
Adam Tkac 05cf27
  resume:
Adam Tkac 05cf27
 	CTRACE("query_find: resume");
Adam Tkac 05cf27
 
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Rate limit these responses to this client.
Tomas Hozza df0cae
+	 * Do not delay counting and handling obvious referrals,
Tomas Hozza df0cae
+	 *	since those won't come here again.
Tomas Hozza df0cae
+	 * Delay handling delegations for which we are certain to recurse and
Tomas Hozza df0cae
+	 *	return here (DNS_R_DELEGATION, not a child of one of our
Tomas Hozza df0cae
+	 *	own zones, and recursion enabled)
Tomas Hozza df0cae
+	 * Count each response at most once.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (client->view->rrl != NULL &&
Tomas Hozza df0cae
+	    ((fname != NULL && dns_name_isabsolute(fname)) ||
Tomas Hozza df0cae
+	     (result == ISC_R_NOTFOUND && !RECURSIONOK(client))) &&
Tomas Hozza df0cae
+	    !(result == DNS_R_DELEGATION && !is_zone && RECURSIONOK(client)) &&
Adam Tkac 05cf27
+	    (client->query.attributes & NS_QUERYATTR_RRL_CHECKED) == 0) {
Adam Tkac 05cf27
+		dns_rdataset_t nc_rdataset;
Adam Tkac 05cf27
+		isc_boolean_t wouldlog;
Adam Tkac 05cf27
+		char log_buf[DNS_RRL_LOG_BUF_LEN];
Tomas Hozza df0cae
+		isc_result_t nc_result, resp_result;
Adam Tkac 05cf27
+		dns_rrl_result_t rrl_result;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+		client->query.attributes |= NS_QUERYATTR_RRL_CHECKED;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+		wouldlog = isc_log_wouldlog(ns_g_lctx, DNS_RRL_LOG_DROP);
Adam Tkac 05cf27
+		tname = fname;
Adam Tkac 05cf27
+		if (result == DNS_R_NXDOMAIN) {
Adam Tkac 05cf27
+			/*
Adam Tkac 05cf27
+			 * Use the database origin name to rate limit NXDOMAIN
Adam Tkac 05cf27
+			 */
Adam Tkac 05cf27
+			if (db != NULL)
Adam Tkac 05cf27
+				tname = dns_db_origin(db);
Tomas Hozza df0cae
+			resp_result = result;
Adam Tkac 05cf27
+		} else if (result == DNS_R_NCACHENXDOMAIN &&
Adam Tkac 05cf27
+			   rdataset != NULL &&
Adam Tkac 05cf27
+			   dns_rdataset_isassociated(rdataset) &&
Adam Tkac 05cf27
+			   (rdataset->attributes &
Adam Tkac 05cf27
+			    DNS_RDATASETATTR_NEGATIVE) != 0) {
Adam Tkac 05cf27
+			/*
Adam Tkac 05cf27
+			 * Try to use owner name in the negative cache SOA.
Adam Tkac 05cf27
+			 */
Adam Tkac 05cf27
+			dns_fixedname_init(&fixed);
Adam Tkac 05cf27
+			dns_rdataset_init(&nc_rdataset);
Adam Tkac 05cf27
+			for (nc_result = dns_rdataset_first(rdataset);
Adam Tkac 05cf27
+			     nc_result == ISC_R_SUCCESS;
Adam Tkac 05cf27
+			     nc_result = dns_rdataset_next(rdataset)) {
Adam Tkac 05cf27
+				dns_ncache_current(rdataset,
Adam Tkac 05cf27
+						   dns_fixedname_name(&fixed),
Adam Tkac 05cf27
+						   &nc_rdataset);
Adam Tkac 05cf27
+				if (nc_rdataset.type == dns_rdatatype_soa) {
Adam Tkac 05cf27
+					dns_rdataset_disassociate(&nc_rdataset);
Adam Tkac 05cf27
+					tname = dns_fixedname_name(&fixed);
Adam Tkac 05cf27
+					break;
Adam Tkac 05cf27
+				}
Adam Tkac 05cf27
+				dns_rdataset_disassociate(&nc_rdataset);
Adam Tkac 05cf27
+			}
Tomas Hozza df0cae
+			resp_result = DNS_R_NXDOMAIN;
Tomas Hozza df0cae
+		} else if (result == DNS_R_NXRRSET ||
Tomas Hozza df0cae
+			   result == DNS_R_EMPTYNAME) {
Tomas Hozza df0cae
+			resp_result = DNS_R_NXRRSET;
Adam Tkac 05cf27
+		} else if (result == DNS_R_DELEGATION) {
Tomas Hozza df0cae
+			resp_result = result;
Tomas Hozza df0cae
+		} else if (result == ISC_R_NOTFOUND) {
Tomas Hozza df0cae
+			/*
Tomas Hozza df0cae
+			 * Handle referral to ".", including when recursion
Tomas Hozza df0cae
+			 * is off or not requested and the hints have not
Tomas Hozza df0cae
+			 * been loaded or we have "additional-from-cache no".
Tomas Hozza df0cae
+			 */
Tomas Hozza df0cae
+			tname = dns_rootname;
Tomas Hozza df0cae
+			resp_result = DNS_R_DELEGATION;
Adam Tkac 05cf27
+		} else {
Tomas Hozza df0cae
+			resp_result = ISC_R_SUCCESS;
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		rrl_result = dns_rrl(client->view, &client->peeraddr,
Adam Tkac 05cf27
+				     ISC_TF((client->attributes
Adam Tkac 05cf27
+					     & NS_CLIENTATTR_TCP) != 0),
Adam Tkac 05cf27
+				     client->message->rdclass, qtype, tname,
Tomas Hozza df0cae
+				     resp_result, client->now,
Adam Tkac 05cf27
+				     wouldlog, log_buf, sizeof(log_buf));
Adam Tkac 05cf27
+		if (rrl_result != DNS_RRL_RESULT_OK) {
Adam Tkac 05cf27
+			/*
Adam Tkac 05cf27
+			 * Log dropped or slipped responses in the query
Adam Tkac 05cf27
+			 * category so that requests are not silently lost.
Adam Tkac 05cf27
+			 * Starts of rate-limited bursts are logged in
Adam Tkac 05cf27
+			 * DNS_LOGCATEGORY_RRL.
Adam Tkac 05cf27
+			 *
Adam Tkac 05cf27
+			 * Dropped responses are counted with dropped queries
Adam Tkac 05cf27
+			 * in QryDropped while slipped responses are counted
Adam Tkac 05cf27
+			 * with other truncated responses in RespTruncated.
Adam Tkac 05cf27
+			 */
Tomas Hozza df0cae
+			if (wouldlog) {
Tomas Hozza df0cae
+				ns_client_log(client,
Tomas Hozza df0cae
+					      NS_LOGCATEGORY_QUERY_EERRORS,
Tomas Hozza df0cae
+					      NS_LOGMODULE_QUERY,
Adam Tkac 05cf27
+					      DNS_RRL_LOG_DROP,
Adam Tkac 05cf27
+					      "%s", log_buf);
Adam Tkac 05cf27
+			}
Adam Tkac 05cf27
+			if (!client->view->rrl->log_only) {
Adam Tkac 05cf27
+				if (rrl_result == DNS_RRL_RESULT_DROP) {
Adam Tkac 05cf27
+					/*
Adam Tkac 05cf27
+					 * These will also be counted in
Adam Tkac 05cf27
+					 * dns_nsstatscounter_dropped
Adam Tkac 05cf27
+					 */
Adam Tkac 05cf27
+					inc_stats(client,
Adam Tkac 05cf27
+						dns_nsstatscounter_ratedropped);
Adam Tkac 05cf27
+					QUERY_ERROR(DNS_R_DROP);
Adam Tkac 05cf27
+				} else {
Adam Tkac 05cf27
+					/*
Adam Tkac 05cf27
+					 * These will also be counted in
Adam Tkac 05cf27
+					 * dns_nsstatscounter_truncatedresp
Adam Tkac 05cf27
+					 */
Adam Tkac 05cf27
+					inc_stats(client,
Adam Tkac 05cf27
+						dns_nsstatscounter_rateslipped);
Adam Tkac 05cf27
+					client->message->flags |=
Adam Tkac 05cf27
+						DNS_MESSAGEFLAG_TC;
Tomas Hozza df0cae
+					if (resp_result == DNS_R_NXDOMAIN)
Tomas Hozza df0cae
+						client->message->rcode =
Tomas Hozza df0cae
+							dns_rcode_nxdomain;
Adam Tkac 05cf27
+				}
Adam Tkac 05cf27
+				goto cleanup;
Adam Tkac 05cf27
+			}
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
 	if (!ISC_LIST_EMPTY(client->view->rpz_zones) &&
Adam Tkac 05cf27
 	    (RECURSIONOK(client) || !client->view->rpz_recursive_only) &&
Adam Tkac 05cf27
 	    rpz_ck_dnssec(client, result, rdataset, sigrdataset) &&
Tomas Hozza df0cae
@@ -7318,12 +7440,14 @@
Adam Tkac 05cf27
 	}
Adam Tkac 05cf27
 
Adam Tkac 05cf27
 	if (eresult != ISC_R_SUCCESS &&
Adam Tkac 05cf27
-	    (!PARTIALANSWER(client) || WANTRECURSION(client))) {
Adam Tkac 05cf27
+	    (!PARTIALANSWER(client) || WANTRECURSION(client)
Adam Tkac 05cf27
+	     || eresult == DNS_R_DROP)) {
Adam Tkac 05cf27
 		if (eresult == DNS_R_DUPLICATE || eresult == DNS_R_DROP) {
Adam Tkac 05cf27
 			/*
Adam Tkac 05cf27
 			 * This was a duplicate query that we are
Adam Tkac 05cf27
-			 * recursing on.  Don't send a response now.
Adam Tkac 05cf27
-			 * The original query will still cause a response.
Adam Tkac 05cf27
+			 * recursing on or the result of rate limiting.
Adam Tkac 05cf27
+			 * Don't send a response now for a duplicate query,
Adam Tkac 05cf27
+			 * because the original will still cause a response.
Adam Tkac 05cf27
 			 */
Adam Tkac 05cf27
 			query_next(client, eresult);
Adam Tkac 05cf27
 		} else {
Tomas Hozza df0cae
diff -r -u bin/named/server.c-orig bin/named/server.c
Tomas Hozza df0cae
--- bin/named/server.c-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/named/server.c	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -1639,6 +1639,168 @@
Adam Tkac c9b941
 	return (ISC_R_SUCCESS);
Adam Tkac 05cf27
 }
Adam Tkac 05cf27
 
Tomas Hozza df0cae
+#define CHECK_RRL(cond, pat, val1, val2)				\
Adam Tkac 05cf27
+	do {								\
Adam Tkac 05cf27
+		if (!(cond)) {						\
Adam Tkac 05cf27
+			cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR,	\
Adam Tkac 05cf27
+				    pat, val1, val2);			\
Adam Tkac 05cf27
+			result = ISC_R_RANGE;				\
Adam Tkac 05cf27
+			goto cleanup;					\
Adam Tkac 05cf27
+		    }							\
Adam Tkac 05cf27
+	} while (0)
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+#define CHECK_RRL_RATE(rate, def, max_rate, name)			\
Tomas Hozza df0cae
+	do {								\
Tomas Hozza df0cae
+		obj = NULL;						\
Tomas Hozza df0cae
+		rrl->rate.str = name;					\
Tomas Hozza df0cae
+		result = cfg_map_get(map, name, &obj);			\
Tomas Hozza df0cae
+		if (result == ISC_R_SUCCESS) {				\
Tomas Hozza df0cae
+			rrl->rate.r = cfg_obj_asuint32(obj);		\
Tomas Hozza df0cae
+			CHECK_RRL(rrl->rate.r <= max_rate,		\
Tomas Hozza df0cae
+				  name" %d > %d",			\
Tomas Hozza df0cae
+				  rrl->rate.r, max_rate);		\
Tomas Hozza df0cae
+		} else {						\
Tomas Hozza df0cae
+			rrl->rate.r = def;				\
Tomas Hozza df0cae
+		}							\
Tomas Hozza df0cae
+		rrl->rate.scaled = rrl->rate.r;				\
Tomas Hozza df0cae
+	} while (0)
Tomas Hozza df0cae
+
Adam Tkac 05cf27
+static isc_result_t
Adam Tkac 05cf27
+configure_rrl(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *map) {
Adam Tkac 05cf27
+	const cfg_obj_t *obj;
Adam Tkac 05cf27
+	dns_rrl_t *rrl;
Adam Tkac 05cf27
+	isc_result_t result;
Adam Tkac 05cf27
+ 	int min_entries, i, j;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Most DNS servers have few clients, but intentinally open
Adam Tkac 05cf27
+	 * recursive and authoritative servers often have many.
Adam Tkac 05cf27
+	 * So start with a small number of entries unless told otherwise
Adam Tkac 05cf27
+	 * to reduce cold-start costs.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	min_entries = 500;
Adam Tkac 05cf27
+	obj = NULL;
Adam Tkac 05cf27
+	result = cfg_map_get(map, "min-table-size", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		min_entries = cfg_obj_asuint32(obj);
Adam Tkac 05cf27
+		if (min_entries < 1)
Adam Tkac 05cf27
+			min_entries = 1;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	result = dns_rrl_init(&rrl, view, min_entries);
Adam Tkac 05cf27
+	if (result != ISC_R_SUCCESS)
Adam Tkac 05cf27
+		return (result);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	i = ISC_MAX(20000, min_entries);
Adam Tkac 05cf27
+	obj = NULL;
Adam Tkac 05cf27
+	result = cfg_map_get(map, "max-table-size", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		i = cfg_obj_asuint32(obj);
Tomas Hozza df0cae
+		CHECK_RRL(i >= min_entries,
Adam Tkac 05cf27
+			  "max-table-size %d < min-table-size %d",
Adam Tkac 05cf27
+			  i, min_entries);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	rrl->max_entries = i;
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+	CHECK_RRL_RATE(responses_per_second, 0, DNS_RRL_MAX_RATE,
Tomas Hozza df0cae
+		       "responses-per-second");
Tomas Hozza df0cae
+	CHECK_RRL_RATE(referrals_per_second,
Tomas Hozza df0cae
+		       rrl->responses_per_second.r, DNS_RRL_MAX_RATE,
Tomas Hozza df0cae
+		       "referrals-per-second");
Tomas Hozza df0cae
+	CHECK_RRL_RATE(nodata_per_second,
Tomas Hozza df0cae
+		       rrl->responses_per_second.r, DNS_RRL_MAX_RATE,
Tomas Hozza df0cae
+		       "nodata-per-second");
Tomas Hozza df0cae
+	CHECK_RRL_RATE(nxdomains_per_second,
Tomas Hozza df0cae
+		       rrl->responses_per_second.r, DNS_RRL_MAX_RATE,
Tomas Hozza df0cae
+		       "nxdomains-per-second");
Tomas Hozza df0cae
+	CHECK_RRL_RATE(errors_per_second,
Tomas Hozza df0cae
+		       rrl->responses_per_second.r, DNS_RRL_MAX_RATE,
Tomas Hozza df0cae
+		       "errors-per-second");
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+	CHECK_RRL_RATE(all_per_second, 0, DNS_RRL_MAX_RATE,
Tomas Hozza df0cae
+		       "all-per-second");
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+	CHECK_RRL_RATE(slip, 2, DNS_RRL_MAX_SLIP,
Tomas Hozza df0cae
+		       "slip");
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	i = 15;
Adam Tkac 05cf27
+	obj = NULL;
Adam Tkac 05cf27
+	result = cfg_map_get(map, "window", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		i = cfg_obj_asuint32(obj);
Tomas Hozza df0cae
+		CHECK_RRL(i >= 1 && i <= DNS_RRL_MAX_WINDOW,
Adam Tkac 05cf27
+			  "window %d < 1 or > %d", i, DNS_RRL_MAX_WINDOW);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	rrl->window = i;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	i = 0;
Adam Tkac 05cf27
+	obj = NULL;
Adam Tkac 05cf27
+	result = cfg_map_get(map, "qps-scale", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		i = cfg_obj_asuint32(obj);
Tomas Hozza df0cae
+		CHECK_RRL(i >= 1, "invalid 'qps-scale %d'%s", i, "");
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	rrl->qps_scale = i;
Adam Tkac 05cf27
+	rrl->qps = 1.0;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	i = 24;
Adam Tkac 05cf27
+	obj = NULL;
Tomas Hozza df0cae
+	result = cfg_map_get(map, "ipv4-prefix-length", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		i = cfg_obj_asuint32(obj);
Tomas Hozza df0cae
+		CHECK_RRL(i >= 8 && i <= 32,
Tomas Hozza df0cae
+			  "invalid 'ipv4-prefix-length %d'%s", i, "");
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	rrl->ipv4_prefixlen = i;
Adam Tkac 05cf27
+	if (i == 32)
Adam Tkac 05cf27
+		rrl->ipv4_mask = 0xffffffff;
Adam Tkac 05cf27
+	else
Adam Tkac 05cf27
+		rrl->ipv4_mask = htonl(0xffffffff << (32-i));
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	i = 56;
Adam Tkac 05cf27
+	obj = NULL;
Tomas Hozza df0cae
+	result = cfg_map_get(map, "ipv6-prefix-length", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		i = cfg_obj_asuint32(obj);
Tomas Hozza df0cae
+		CHECK_RRL(i >= 16 && i <= DNS_RRL_MAX_PREFIX,
Tomas Hozza df0cae
+			  "ipv6-prefix-length %d < 16 or > %d",
Adam Tkac 05cf27
+			  i, DNS_RRL_MAX_PREFIX);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	rrl->ipv6_prefixlen = i;
Adam Tkac 05cf27
+	for (j = 0; j < 4; ++j) {
Adam Tkac 05cf27
+		if (i <= 0) {
Adam Tkac 05cf27
+			rrl->ipv6_mask[j] = 0;
Adam Tkac 05cf27
+		} else if (i < 32) {
Adam Tkac 05cf27
+			rrl->ipv6_mask[j] = htonl(0xffffffff << (32-i));
Adam Tkac 05cf27
+		} else {
Adam Tkac 05cf27
+			rrl->ipv6_mask[j] = 0xffffffff;
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		i -= 32;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	obj = NULL;
Adam Tkac 05cf27
+	result = cfg_map_get(map, "exempt-clients", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		result = cfg_acl_fromconfig(obj, config, ns_g_lctx,
Adam Tkac 05cf27
+					    ns_g_aclconfctx, ns_g_mctx,
Adam Tkac 05cf27
+					    0, &rrl->exempt);
Tomas Hozza df0cae
+		CHECK_RRL(result == ISC_R_SUCCESS,
Adam Tkac 05cf27
+			  "invalid %s%s", "address match list", "");
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	obj = NULL;
Adam Tkac 05cf27
+	result = cfg_map_get(map, "log-only", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS && cfg_obj_asboolean(obj))
Adam Tkac 05cf27
+		rrl->log_only = ISC_TRUE;
Adam Tkac 05cf27
+	else
Adam Tkac 05cf27
+		rrl->log_only = ISC_FALSE;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	return (ISC_R_SUCCESS);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+ cleanup:
Adam Tkac 05cf27
+	dns_rrl_view_destroy(view);
Adam Tkac 05cf27
+	return (result);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
 /*
Adam Tkac 05cf27
  * Configure 'view' according to 'vconfig', taking defaults from 'config'
Adam Tkac 05cf27
  * where values are missing in 'vconfig'.
Tomas Hozza df0cae
@@ -3043,6 +3205,14 @@
Adam Tkac 05cf27
 		}
Adam Tkac 05cf27
 	}
Adam Tkac 05cf27
 
Adam Tkac 05cf27
+	obj = NULL;
Adam Tkac 05cf27
+	result = ns_config_get(maps, "rate-limit", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		result = configure_rrl(view, config, obj);
Adam Tkac 05cf27
+		if (result != ISC_R_SUCCESS)
Adam Tkac 05cf27
+			goto cleanup;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
 	result = ISC_R_SUCCESS;
Adam Tkac 05cf27
 
Adam Tkac 05cf27
  cleanup:
Tomas Hozza df0cae
diff -r -u bin/named/statschannel.c-orig bin/named/statschannel.c
Tomas Hozza df0cae
--- bin/named/statschannel.c-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/named/statschannel.c	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -206,6 +206,10 @@
Adam Tkac 05cf27
 	SET_NSSTATDESC(updatebadprereq,
Adam Tkac 05cf27
 		       "updates rejected due to prerequisite failure",
Adam Tkac 05cf27
 		       "UpdateBadPrereq");
Adam Tkac 05cf27
+	SET_NSSTATDESC(ratedropped, "responses dropped for rate limits",
Adam Tkac 05cf27
+		       "RateDropped");
Adam Tkac 05cf27
+	SET_NSSTATDESC(rateslipped, "responses truncated for rate limits",
Adam Tkac 05cf27
+		       "RateSlipped");
Adam Tkac c9b941
 	SET_NSSTATDESC(rpz_rewrites, "response policy zone rewrites",
Adam Tkac c9b941
 		       "RPZRewrites");
Adam Tkac 05cf27
 	INSIST(i == dns_nsstatscounter_max);
Tomas Hozza df0cae
diff -r -u bin/tests/system/README-orig bin/tests/system/README
Tomas Hozza df0cae
--- bin/tests/system/README-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/README	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -17,6 +17,7 @@
Tomas Hozza df0cae
   nsupdate/	Dynamic update and IXFR tests
Tomas Hozza df0cae
   resolver/     Regression tests for resolver bugs that have been fixed
Tomas Hozza df0cae
 		(not a complete resolver test suite)
Tomas Hozza df0cae
+  rrl/		query rate limiting
Tomas Hozza df0cae
   rpz/		Tests of response policy zone (RPZ) rewriting
Tomas Hozza df0cae
   stub/		Tests of stub zone functionality
Tomas Hozza df0cae
   unknown/	Unknown type and class tests
Tomas Hozza df0cae
diff -r -u bin/tests/system/conf.sh.in-orig bin/tests/system/conf.sh.in
Tomas Hozza df0cae
--- bin/tests/system/conf.sh.in-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/conf.sh.in	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -62,7 +62,7 @@
Adam Tkac c9b941
          database dlv dlvauto dlz dlzexternal dname dns64 dnssec ecdsa
Adam Tkac c9b941
          formerr forward glue gost ixfr inline limits logfileconfig
Adam Tkac c9b941
          lwresd masterfile masterformat metadata notify nsupdate pending
Adam Tkac c9b941
-	 pkcs11 redirect resolver rndc rpz rrsetorder rsabigexponent
Adam Tkac c9b941
+	 pkcs11 redirect resolver rndc rpz rrl rrsetorder rsabigexponent
Tomas Hozza 60039a
 	 smartsign sortlist spf staticstub stub tkey tsig tsiggss unknown
Adam Tkac c9b941
 	 upforwd verify views wildcard xfer xferquota zonechecks"
Adam Tkac c9b941
 
Tomas Hozza df0cae
diff -r -u bin/tests/system/rrl/clean.sh-orig bin/tests/system/rrl/clean.sh
Tomas Hozza df0cae
--- bin/tests/system/rrl/clean.sh-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/rrl/clean.sh	2004-01-01 00:00:00.000000000 +0000
Adam Tkac 05cf27
@@ -0,0 +1,21 @@
Adam Tkac c9b941
+# Copyright (C) 2012, 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+#
Adam Tkac 05cf27
+# Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+# purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+# copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+#
Adam Tkac 05cf27
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+# PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# Clean up after rrl tests.
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+rm -f dig.out*
Tomas Hozza df0cae
+rm -f  */named.memstats */named.run */named.stats */log-* */session.key
Adam Tkac 05cf27
+rm -f ns3/bl*.db */*.jnl */*.core */*.pid
Tomas Hozza df0cae
diff -r -u bin/tests/system/rrl/ns1/named.conf-orig bin/tests/system/rrl/ns1/named.conf
Tomas Hozza df0cae
--- bin/tests/system/rrl/ns1/named.conf-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/rrl/ns1/named.conf	2004-01-01 00:00:00.000000000 +0000
Adam Tkac 05cf27
@@ -0,0 +1,32 @@
Adam Tkac 05cf27
+/*
Adam Tkac c9b941
+ * Copyright (C) 2012, 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+ *
Adam Tkac 05cf27
+ * Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+ * purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+ * copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+ *
Adam Tkac 05cf27
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+ * PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+controls { /* empty */ };
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+options {
Adam Tkac 05cf27
+	query-source address 10.53.0.1;
Adam Tkac 05cf27
+	notify-source 10.53.0.1;
Adam Tkac 05cf27
+	transfer-source 10.53.0.1;
Adam Tkac 05cf27
+	port 5300;
Adam Tkac 05cf27
+	session-keyfile "session.key";
Adam Tkac 05cf27
+	pid-file "named.pid";
Adam Tkac 05cf27
+	listen-on { 10.53.0.1; };
Adam Tkac 05cf27
+	listen-on-v6 { none; };
Adam Tkac 05cf27
+	notify no;
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+zone "." {type master; file "root.db";};
Tomas Hozza df0cae
diff -r -u bin/tests/system/rrl/ns1/root.db-orig bin/tests/system/rrl/ns1/root.db
Tomas Hozza df0cae
--- bin/tests/system/rrl/ns1/root.db-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/rrl/ns1/root.db	2004-01-01 00:00:00.000000000 +0000
Adam Tkac 05cf27
@@ -0,0 +1,31 @@
Adam Tkac c9b941
+; Copyright (C) 2012, 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+;
Adam Tkac 05cf27
+; Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+; purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+; copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+;
Adam Tkac 05cf27
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+; PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+$TTL	120
Adam Tkac 05cf27
+@		SOA	ns. hostmaster.ns. ( 1 3600 1200 604800 60 )
Adam Tkac 05cf27
+@		NS	ns.
Adam Tkac 05cf27
+ns.		A	10.53.0.1
Adam Tkac 05cf27
+.		A	10.53.0.1
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+; limit responses from here
Adam Tkac 05cf27
+tld2.		NS	ns.tld2.
Adam Tkac 05cf27
+ns.tld2.	A	10.53.0.2
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+; limit recursion to here
Adam Tkac 05cf27
+tld3.		NS	ns.tld3.
Adam Tkac 05cf27
+ns.tld3.	A	10.53.0.3
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+; generate SERVFAIL
Adam Tkac 05cf27
+tld4.		NS	ns.tld3.
Tomas Hozza df0cae
diff -r -u bin/tests/system/rrl/ns2/hints-orig bin/tests/system/rrl/ns2/hints
Tomas Hozza df0cae
--- bin/tests/system/rrl/ns2/hints-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/rrl/ns2/hints	2004-01-01 00:00:00.000000000 +0000
Adam Tkac 05cf27
@@ -0,0 +1,18 @@
Adam Tkac c9b941
+; Copyright (C) 2012, 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+;
Adam Tkac 05cf27
+; Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+; purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+; copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+;
Adam Tkac 05cf27
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+; PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+.	0	NS	ns1.
Adam Tkac 05cf27
+ns1.	0	A	10.53.0.1
Tomas Hozza df0cae
diff -r -u bin/tests/system/rrl/ns2/named.conf-orig bin/tests/system/rrl/ns2/named.conf
Tomas Hozza df0cae
--- bin/tests/system/rrl/ns2/named.conf-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/rrl/ns2/named.conf	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -0,0 +1,71 @@
Adam Tkac 05cf27
+/*
Adam Tkac c9b941
+ * Copyright (C) 2012, 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+ *
Adam Tkac 05cf27
+ * Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+ * purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+ * copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+ *
Adam Tkac 05cf27
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+ * PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+controls { /* empty */ };
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+options {
Adam Tkac 05cf27
+	query-source address 10.53.0.2;
Adam Tkac 05cf27
+	notify-source 10.53.0.2;
Adam Tkac 05cf27
+	transfer-source 10.53.0.2;
Adam Tkac 05cf27
+	port 5300;
Adam Tkac 05cf27
+	session-keyfile "session.key";
Adam Tkac 05cf27
+	pid-file "named.pid";
Adam Tkac 05cf27
+	statistics-file	"named.stats";
Adam Tkac 05cf27
+	listen-on { 10.53.0.2; };
Adam Tkac 05cf27
+	listen-on-v6 { none; };
Adam Tkac 05cf27
+	notify no;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	rate-limit {
Adam Tkac 05cf27
+	    responses-per-second 2;
Tomas Hozza df0cae
+	    all-per-second 50;
Adam Tkac 05cf27
+	    slip 3;
Adam Tkac 05cf27
+	    exempt-clients { 10.53.0.7; };
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+	    // small enough to force a table expansion
Tomas Hozza df0cae
+	    min-table-size 75;
Adam Tkac 05cf27
+	};
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+	additional-from-cache no;
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+key rndc_key {
Adam Tkac 05cf27
+	secret "1234abcd8765";
Adam Tkac 05cf27
+	algorithm hmac-md5;
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+controls {
Adam Tkac 05cf27
+	inet 10.53.0.2 port 9953 allow { any; } keys { rndc_key; };
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * These log settings have no effect unless "-g" is removed from ../../start.pl
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+logging {
Adam Tkac 05cf27
+	channel debug {
Adam Tkac 05cf27
+	    file "log-debug";
Adam Tkac 05cf27
+	    print-category yes; print-severity yes; severity debug 10;
Adam Tkac 05cf27
+	};
Adam Tkac 05cf27
+	channel queries {
Adam Tkac 05cf27
+	    file "log-queries";
Adam Tkac 05cf27
+	    print-category yes; print-severity yes; severity info;
Adam Tkac 05cf27
+	};
Adam Tkac 05cf27
+	category rate-limit { debug; queries; };
Adam Tkac 05cf27
+	category queries { debug; queries; };
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+zone "." { type hint; file "hints"; };
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+zone "tld2."{ type master; file "tld2.db"; };
Tomas Hozza df0cae
diff -r -u bin/tests/system/rrl/ns2/tld2.db-orig bin/tests/system/rrl/ns2/tld2.db
Tomas Hozza df0cae
--- bin/tests/system/rrl/ns2/tld2.db-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/rrl/ns2/tld2.db	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -0,0 +1,47 @@
Adam Tkac c9b941
+; Copyright (C) 2012, 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+;
Adam Tkac 05cf27
+; Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+; purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+; copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+;
Adam Tkac 05cf27
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+; PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+; rate limit response from this zone
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+$TTL	120
Adam Tkac 05cf27
+@		SOA	tld2.  hostmaster.ns.tld2. ( 1 3600 1200 604800 60 )
Adam Tkac 05cf27
+		NS	ns
Adam Tkac 05cf27
+		NS	.
Adam Tkac 05cf27
+ns		A	10.53.0.2
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+; basic rate limiting
Adam Tkac c9b941
+a1		A	192.0.2.1
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+; wildcards
Adam Tkac c9b941
+*.a2		A	192.0.2.2
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+; a3 is in tld3
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+; a4 does not exist to give NXDOMAIN
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+; a5 for TCP requests
Adam Tkac c9b941
+a5		A	192.0.2.5
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+; a6 for whitelisted clients
Adam Tkac c9b941
+a6		A	192.0.2.6
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+; a7 for SERVFAIL
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+; a8 for NODATA
Tomas Hozza df0cae
+a8		A	192.0.2.8
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+; a9 for all-per-second limit
Tomas Hozza df0cae
+$GENERATE 101-180 all$.a9 A 192.0.2.8
Tomas Hozza df0cae
diff -r -u bin/tests/system/rrl/ns3/hints-orig bin/tests/system/rrl/ns3/hints
Tomas Hozza df0cae
--- bin/tests/system/rrl/ns3/hints-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/rrl/ns3/hints	2004-01-01 00:00:00.000000000 +0000
Adam Tkac 05cf27
@@ -0,0 +1,18 @@
Adam Tkac c9b941
+; Copyright (C) 2012, 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+;
Adam Tkac 05cf27
+; Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+; purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+; copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+;
Adam Tkac 05cf27
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+; PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+.	0	NS	ns1.
Adam Tkac 05cf27
+ns1.	0	A	10.53.0.1
Tomas Hozza df0cae
diff -r -u bin/tests/system/rrl/ns3/named.conf-orig bin/tests/system/rrl/ns3/named.conf
Tomas Hozza df0cae
--- bin/tests/system/rrl/ns3/named.conf-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/rrl/ns3/named.conf	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -0,0 +1,50 @@
Adam Tkac 05cf27
+/*
Adam Tkac c9b941
+ * Copyright (C) 2012, 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+ *
Adam Tkac 05cf27
+ * Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+ * purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+ * copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+ *
Adam Tkac 05cf27
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+ * PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+controls { /* empty */ };
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+options {
Adam Tkac 05cf27
+	query-source address 10.53.0.3;
Adam Tkac 05cf27
+	notify-source 10.53.0.3;
Adam Tkac 05cf27
+	transfer-source 10.53.0.3;
Adam Tkac 05cf27
+	port 5300;
Adam Tkac 05cf27
+	session-keyfile "session.key";
Adam Tkac 05cf27
+	pid-file "named.pid";
Adam Tkac 05cf27
+	listen-on { 10.53.0.3; };
Adam Tkac 05cf27
+	listen-on-v6 { none; };
Adam Tkac 05cf27
+	notify no;
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+	// check that all of the options are parsed without limiting anything
Tomas Hozza df0cae
+	rate-limit {
Tomas Hozza df0cae
+	    responses-per-second 200;
Tomas Hozza df0cae
+	    referrals-per-second 220;
Tomas Hozza df0cae
+	    nodata-per-second 230;
Tomas Hozza df0cae
+	    nxdomains-per-second 240;
Tomas Hozza df0cae
+	    errors-per-second 250;
Tomas Hozza df0cae
+	    all-per-second 700;
Tomas Hozza df0cae
+	    ipv4-prefix-length 24;
Tomas Hozza df0cae
+	    ipv6-prefix-length 64;
Tomas Hozza df0cae
+	    qps-scale 10;
Tomas Hozza df0cae
+	    window 1;
Tomas Hozza df0cae
+	    max-table-size 1000;
Tomas Hozza df0cae
+	};
Tomas Hozza df0cae
+
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+zone "." { type hint; file "hints"; };
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+zone "tld3."{ type master; file "tld3.db"; };
Tomas Hozza df0cae
diff -r -u bin/tests/system/rrl/ns3/tld3.db-orig bin/tests/system/rrl/ns3/tld3.db
Tomas Hozza df0cae
--- bin/tests/system/rrl/ns3/tld3.db-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/rrl/ns3/tld3.db	2004-01-01 00:00:00.000000000 +0000
Adam Tkac 05cf27
@@ -0,0 +1,25 @@
Adam Tkac c9b941
+; Copyright (C) 2012, 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+;
Adam Tkac 05cf27
+; Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+; purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+; copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+;
Adam Tkac 05cf27
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+; PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+; rate limit response from this zone
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+$TTL	120
Adam Tkac 05cf27
+@		SOA	tld3.  hostmaster.ns.tld3. ( 1 3600 1200 604800 60 )
Adam Tkac 05cf27
+		NS	ns
Adam Tkac 05cf27
+		NS	.
Adam Tkac 05cf27
+ns		A	10.53.0.3
Adam Tkac 05cf27
+
Adam Tkac c9b941
+*.a3		A	192.0.3.3
Tomas Hozza df0cae
diff -r -u bin/tests/system/rrl/setup.sh-orig bin/tests/system/rrl/setup.sh
Tomas Hozza df0cae
--- bin/tests/system/rrl/setup.sh-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/rrl/setup.sh	2004-01-01 00:00:00.000000000 +0000
Adam Tkac 05cf27
@@ -0,0 +1,21 @@
Adam Tkac 05cf27
+#!/bin/sh
Adam Tkac 05cf27
+#
Adam Tkac c9b941
+# Copyright (C) 2012, 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+#
Adam Tkac 05cf27
+# Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+# purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+# copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+#
Adam Tkac 05cf27
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+# PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+SYSTEMTESTTOP=..
Adam Tkac 05cf27
+. $SYSTEMTESTTOP/conf.sh
Adam Tkac 05cf27
+. ./clean.sh
Adam Tkac 05cf27
+
Tomas Hozza df0cae
diff -r -u bin/tests/system/rrl/tests.sh-orig bin/tests/system/rrl/tests.sh
Tomas Hozza df0cae
--- bin/tests/system/rrl/tests.sh-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ bin/tests/system/rrl/tests.sh	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -0,0 +1,258 @@
Adam Tkac c9b941
+# Copyright (C) 2012, 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+#
Adam Tkac 05cf27
+# Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+# purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+# copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+#
Adam Tkac 05cf27
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+# PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# test response rate limiting
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+SYSTEMTESTTOP=..
Adam Tkac 05cf27
+. $SYSTEMTESTTOP/conf.sh
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#set -x
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+ns1=10.53.0.1			    # root, defining the others
Adam Tkac 05cf27
+ns2=10.53.0.2			    # test server
Adam Tkac 05cf27
+ns3=10.53.0.3			    # secondary test server
Adam Tkac 05cf27
+ns7=10.53.0.7			    # whitelisted client
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+USAGE="$0: [-x]"
Adam Tkac 05cf27
+while getopts "x" c; do
Adam Tkac 05cf27
+    case $c in
Adam Tkac 05cf27
+	x) set -x;;
Adam Tkac 05cf27
+	*) echo "$USAGE" 1>&2; exit 1;;
Adam Tkac 05cf27
+    esac
Adam Tkac 05cf27
+done
Adam Tkac 05cf27
+shift `expr $OPTIND - 1 || true`
Adam Tkac 05cf27
+if test "$#" -ne 0; then
Adam Tkac 05cf27
+    echo "$USAGE" 1>&2
Adam Tkac 05cf27
+    exit 1
Adam Tkac 05cf27
+fi
Adam Tkac 05cf27
+# really quit on control-C
Adam Tkac 05cf27
+trap 'exit 1' 1 2 15
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+ret=0
Adam Tkac 05cf27
+setret () {
Adam Tkac 05cf27
+    ret=1
Adam Tkac 05cf27
+    echo "$*"
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# Wait until soon after the start of a second to make results consistent.
Adam Tkac 05cf27
+#   The start of a second credits a rate limit.
Adam Tkac 05cf27
+#   This would be far easier in C or by assuming a modern version of perl.
Adam Tkac 05cf27
+sec_start () {
Adam Tkac 05cf27
+    START=`date`
Adam Tkac 05cf27
+    while true; do
Adam Tkac 05cf27
+	NOW=`date`
Adam Tkac 05cf27
+	if test "$START" != "$NOW"; then
Adam Tkac 05cf27
+	    return
Adam Tkac 05cf27
+	fi
Adam Tkac 05cf27
+	$PERL -e 'select(undef, undef, undef, 0.05)' || true
Adam Tkac 05cf27
+    done
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+# turn off ${HOME}/.digrc
Tomas Hozza df0cae
+HOME=/dev/null; export HOME
Tomas Hozza df0cae
+
Adam Tkac 05cf27
+#   $1=result name  $2=domain name  $3=dig options
Adam Tkac 05cf27
+digcmd () {
Adam Tkac 05cf27
+    OFILE=$1; shift
Adam Tkac 05cf27
+    DIG_DOM=$1; shift
Tomas Hozza df0cae
+    ARGS="+nosearch +time=1 +tries=1 +ignore -p 5300 $* $DIG_DOM @$ns2"
Adam Tkac 05cf27
+    #echo I:dig $ARGS 1>&2
Adam Tkac 05cf27
+    START=`date +%y%m%d%H%M.%S`
Adam Tkac 05cf27
+    RESULT=`$DIG $ARGS 2>&1 | tee $OFILE=TEMP				\
Tomas Hozza df0cae
+	    | sed -n -e '/^;; AUTHORITY/,/^$/d'				\
Tomas Hozza df0cae
+		-e '/^;; ADDITIONAL/,/^$/d'				\
Tomas Hozza df0cae
+		-e  's/^[^;].*	\([^	 ]\{1,\}\)$/\1/p'		\
Adam Tkac 05cf27
+		-e 's/;; flags.* tc .*/TC/p'				\
Adam Tkac 05cf27
+		-e 's/;; .* status: NXDOMAIN.*/NXDOMAIN/p'		\
Adam Tkac 05cf27
+		-e 's/;; .* status: SERVFAIL.*/SERVFAIL/p'		\
Adam Tkac 05cf27
+		-e 's/;; connection timed out.*/drop/p'			\
Adam Tkac 05cf27
+		-e 's/;; communications error to.*/drop/p'		\
Adam Tkac 05cf27
+	    | tr -d '\n'`
Adam Tkac 05cf27
+    mv "$OFILE=TEMP" "$OFILE=$RESULT"
Adam Tkac 05cf27
+    touch -t $START "$OFILE=$RESULT"
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#   $1=number of tests  $2=target domain  $3=dig options
Tomas Hozza df0cae
+QNUM=1
Adam Tkac 05cf27
+burst () {
Adam Tkac 05cf27
+    BURST_LIMIT=$1; shift
Adam Tkac 05cf27
+    BURST_DOM_BASE="$1"; shift
Adam Tkac 05cf27
+    while test "$BURST_LIMIT" -ge 1; do
Tomas Hozza df0cae
+	CNT=`expr "00$QNUM" : '.*\(...\)'`
Adam Tkac 05cf27
+	eval BURST_DOM="$BURST_DOM_BASE"
Adam Tkac 05cf27
+	FILE="dig.out-$BURST_DOM-$CNT"
Adam Tkac 05cf27
+	digcmd $FILE $BURST_DOM $* &
Tomas Hozza df0cae
+	QNUM=`expr $QNUM + 1`
Adam Tkac 05cf27
+	BURST_LIMIT=`expr "$BURST_LIMIT" - 1`
Adam Tkac 05cf27
+    done
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#   $1=domain  $2=IP address  $3=# of IP addresses  $4=TC  $5=drop
Adam Tkac 05cf27
+#	$6=NXDOMAIN  $7=SERVFAIL or other errors
Adam Tkac 05cf27
+ck_result() {
Adam Tkac 05cf27
+    BAD=
Adam Tkac 05cf27
+    wait
Tomas Hozza df0cae
+    ADDRS=`ls dig.out-$1-*=$2				2>/dev/null | wc -l`
Tomas Hozza df0cae
+    # count simple truncated and truncated NXDOMAIN as TC
Tomas Hozza df0cae
+    TC=`ls dig.out-$1-*=TC dig.out-$1-*=NXDOMAINTC	2>/dev/null | wc -l`
Tomas Hozza df0cae
+    DROP=`ls dig.out-$1-*=drop				2>/dev/null | wc -l`
Tomas Hozza df0cae
+    # count NXDOMAIN and truncated NXDOMAIN as NXDOMAIN
Tomas Hozza df0cae
+    NXDOMAIN=`ls dig.out-$1-*=NXDOMAIN  dig.out-$1-*=NXDOMAINTC	2>/dev/null \
Tomas Hozza df0cae
+							| wc -l`
Tomas Hozza df0cae
+    SERVFAIL=`ls dig.out-$1-*=SERVFAIL			2>/dev/null | wc -l`
Adam Tkac 05cf27
+    if test $ADDRS -ne "$3"; then
Tomas Hozza df0cae
+	setret "I:"$ADDRS" instead of $3 '$2' responses for $1"
Adam Tkac 05cf27
+	BAD=yes
Adam Tkac 05cf27
+    fi
Adam Tkac 05cf27
+    if test $TC -ne "$4"; then
Tomas Hozza df0cae
+	setret "I:"$TC" instead of $4 truncation responses for $1"
Adam Tkac 05cf27
+	BAD=yes
Adam Tkac 05cf27
+    fi
Adam Tkac 05cf27
+    if test $DROP -ne "$5"; then
Tomas Hozza df0cae
+	setret "I:"$DROP" instead of $5 dropped responses for $1"
Adam Tkac 05cf27
+	BAD=yes
Adam Tkac 05cf27
+    fi
Adam Tkac 05cf27
+    if test $NXDOMAIN -ne "$6"; then
Tomas Hozza df0cae
+	setret "I:"$NXDOMAIN" instead of $6 NXDOMAIN responses for $1"
Adam Tkac 05cf27
+	BAD=yes
Adam Tkac 05cf27
+    fi
Adam Tkac 05cf27
+    if test $SERVFAIL -ne "$7"; then
Tomas Hozza df0cae
+	setret "I:"$SERVFAIL" instead of $7 error responses for $1"
Adam Tkac 05cf27
+	BAD=yes
Adam Tkac 05cf27
+    fi
Adam Tkac 05cf27
+    if test -z "$BAD"; then
Adam Tkac 05cf27
+	rm -f dig.out-$1-*
Adam Tkac 05cf27
+    fi
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+ckstats () {
Tomas Hozza df0cae
+    LABEL="$1"; shift
Tomas Hozza df0cae
+    TYPE="$1"; shift
Tomas Hozza df0cae
+    EXPECTED="$1"; shift
Tomas Hozza df0cae
+    C=`sed -n -e "s/[	 ]*\([0-9]*\).responses $TYPE for rate limits.*/\1/p"  \
Tomas Hozza df0cae
+	    ns2/named.stats | tail -1`
Tomas Hozza df0cae
+    C=`expr 0$C + 0`
Tomas Hozza df0cae
+    if test "$C" -ne $EXPECTED; then
Tomas Hozza df0cae
+	setret "I:wrong $LABEL $TYPE statistics of $C instead of $EXPECTED"
Tomas Hozza df0cae
+    fi
Tomas Hozza df0cae
+}
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+
Adam Tkac 05cf27
+#########
Adam Tkac 05cf27
+sec_start
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+# Tests of referrals to "." must be done before the hints are loaded
Tomas Hozza df0cae
+#   or with "additional-from-cache no"
Tomas Hozza df0cae
+burst 5 a1.tld3 +norec
Adam Tkac 05cf27
+# basic rate limiting
Adam Tkac 05cf27
+burst 3 a1.tld2
Adam Tkac 05cf27
+# 1 second delay allows an additional response.
Adam Tkac 05cf27
+sleep 1
Tomas Hozza df0cae
+burst 10 a1.tld2
Tomas Hozza df0cae
+# Request 30 different qnames to try a wildcard.
Adam Tkac 05cf27
+burst 30 'x$CNT.a2.tld2'
Tomas Hozza df0cae
+# These should be counted and limited but are not.  See RT33138.
Tomas Hozza df0cae
+burst 10 'y.x$CNT.a2.tld2'
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#					IP      TC      drop  NXDOMAIN SERVFAIL
Tomas Hozza df0cae
+# referrals to "."
Tomas Hozza df0cae
+ck_result   a1.tld3	''		2	1	2	0	0
Tomas Hozza df0cae
+# check 13 results including 1 second delay that allows an additional response
Tomas Hozza df0cae
+ck_result   a1.tld2	192.0.2.1	3	4	6	0	0
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# Check the wild card answers.
Adam Tkac 05cf27
+# The parent name of the 30 requests is counted.
Adam Tkac c9b941
+ck_result 'x*.a2.tld2'	192.0.2.2	2	10	18	0	0
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+# These should be limited but are not.  See RT33138.
Tomas Hozza df0cae
+ck_result 'y.x*.a2.tld2' 192.0.2.2	10	0	0	0	0
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#########
Adam Tkac 05cf27
+sec_start
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+burst 10 'x.a3.tld3'
Tomas Hozza df0cae
+burst 10 'y$CNT.a3.tld3'
Tomas Hozza df0cae
+burst 10 'z$CNT.a4.tld2'
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+# 10 identical recursive responses are limited
Tomas Hozza df0cae
+ck_result 'x.a3.tld3'	192.0.3.3	2	3	5	0	0
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+# 10 different recursive responses are not limited
Tomas Hozza df0cae
+ck_result 'y*.a3.tld3'	192.0.3.3	10	0	0	0	0
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+# 10 different NXDOMAIN responses are limited based on the parent name.
Tomas Hozza df0cae
+#   We count 13 responses because we count truncated NXDOMAIN responses
Tomas Hozza df0cae
+#   as both truncated and NXDOMAIN.
Tomas Hozza df0cae
+ck_result 'z*.a4.tld2'	x		0	3	5	5	0
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats
Tomas Hozza df0cae
+ckstats first dropped 36
Tomas Hozza df0cae
+ckstats first truncated 21
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#########
Adam Tkac 05cf27
+sec_start
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+burst 10 a5.tld2 +tcp
Tomas Hozza df0cae
+burst 10 a6.tld2 -b $ns7
Tomas Hozza df0cae
+burst 10 a7.tld4
Tomas Hozza df0cae
+burst 2 a8.tld2 AAAA
Tomas Hozza df0cae
+burst 2 a8.tld2 TXT
Tomas Hozza df0cae
+burst 2 a8.tld2 SPF
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+#					IP      TC      drop  NXDOMAIN SERVFAIL
Adam Tkac 05cf27
+# TCP responses are not rate limited
Tomas Hozza df0cae
+ck_result a5.tld2	192.0.2.5	10	0	0	0	0
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# whitelisted client is not rate limited
Tomas Hozza df0cae
+ck_result a6.tld2	192.0.2.6	10	0	0	0	0
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+# Errors such as SERVFAIL are rate limited.
Tomas Hozza df0cae
+ck_result a7.tld4	x		0	0	8	0	2
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+# NODATA responses are counted as the same regardless of qtype.
Tomas Hozza df0cae
+ck_result a8.tld2	''		2	2	2	0	0
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats
Tomas Hozza df0cae
+ckstats second dropped 46
Tomas Hozza df0cae
+ckstats second truncated 23
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#########
Adam Tkac 05cf27
+sec_start
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+#					IP      TC      drop  NXDOMAIN SERVFAIL
Adam Tkac 05cf27
+# all-per-second
Adam Tkac 05cf27
+#   The qnames are all unique but the client IP address is constant.
Tomas Hozza df0cae
+QNUM=101
Tomas Hozza df0cae
+burst 60 'all$CNT.a9.tld2'
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+ck_result 'a*.a9.tld2'	192.0.2.8	50	0	10	0	0
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats
Tomas Hozza df0cae
+ckstats final dropped 56
Tomas Hozza df0cae
+ckstats final truncated 23
Tomas Hozza df0cae
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+echo "I:exit status: $ret"
Adam Tkac c9b941
+# exit $ret
Adam Tkac c9b941
+[ $ret -ne 0 ] && echo "I:test failure overridden"
Adam Tkac c9b941
+exit 0
Tomas Hozza df0cae
diff -r -u doc/arm/Bv9ARM-book.xml-orig doc/arm/Bv9ARM-book.xml
Tomas Hozza df0cae
--- doc/arm/Bv9ARM-book.xml-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ doc/arm/Bv9ARM-book.xml	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -4818,6 +4818,32 @@
Adam Tkac 05cf27
 		    </para>
Adam Tkac 05cf27
 		  </entry>
Adam Tkac 05cf27
 		</row>
Adam Tkac 05cf27
+                <row rowsep="0">
Adam Tkac 05cf27
+                  <entry colname="1">
Adam Tkac 05cf27
+                    <para><command>rate-limit</command></para>
Adam Tkac 05cf27
+                  </entry>
Adam Tkac 05cf27
+		  <entry colname="2">
Adam Tkac 05cf27
+		    <para>
Adam Tkac 05cf27
+		      The start, periodic, and final notices of the
Adam Tkac 05cf27
+		      rate limiting of a stream of responses are logged at
Adam Tkac 05cf27
+		      <command>info</command> severity in this category.
Adam Tkac 05cf27
+		      These messages include a hash value of the domain name
Adam Tkac 05cf27
+		      of the response and the name itself,
Adam Tkac 05cf27
+		      except when there is insufficient memory to record
Adam Tkac 05cf27
+		      the name for the final notice
Adam Tkac 05cf27
+		      The final notice is normally delayed until about one
Adam Tkac 05cf27
+		      minute after rate limit stops.
Adam Tkac 05cf27
+		      A lack of memory can hurry the final notice,
Adam Tkac 05cf27
+		      in which case it starts with an asterisk (*).
Adam Tkac 05cf27
+		      Various internal events are logged at debug 1 level
Adam Tkac 05cf27
+		      and higher.
Adam Tkac 05cf27
+		    </para>
Adam Tkac 05cf27
+		    <para>
Adam Tkac 05cf27
+		      Rate limiting of individual requests
Tomas Hozza df0cae
+		      is logged in the <command>query-errors</command> category.
Adam Tkac 05cf27
+		    </para>
Adam Tkac 05cf27
+		  </entry>
Adam Tkac 05cf27
+		</row>
Adam Tkac 05cf27
 	      
Adam Tkac 05cf27
 	    </tgroup>
Adam Tkac 05cf27
 	  </informaltable>
Tomas Hozza df0cae
@@ -5318,7 +5344,7 @@
Tomas Hozza df0cae
     <optional> match-mapped-addresses <replaceable>yes_or_no</replaceable>; </optional>
Tomas Hozza df0cae
     <optional> filter-aaaa-on-v4 ( <replaceable>yes_or_no</replaceable> | <replaceable>break-dnssec</replaceable> ); </optional>
Tomas Hozza df0cae
     <optional> filter-aaaa { <replaceable>address_match_list</replaceable> }; </optional>
Tomas Hozza df0cae
-    <optional> dns64 <replaceable>IPv6-prefix</replaceable> {
Tomas Hozza df0cae
+    <optional> dns64 <replaceable>ipv6-prefix</replaceable> {
Tomas Hozza df0cae
 	<optional> clients { <replaceable>address_match_list</replaceable> }; </optional>
Tomas Hozza df0cae
 	<optional> mapped { <replaceable>address_match_list</replaceable> }; </optional>
Tomas Hozza df0cae
         <optional> exclude { <replaceable>address_match_list</replaceable> }; </optional>
Tomas Hozza df0cae
@@ -5351,6 +5377,23 @@
Adam Tkac 05cf27
     <optional> resolver-query-timeout <replaceable>number</replaceable> ; </optional>
Adam Tkac 05cf27
     <optional> deny-answer-addresses { <replaceable>address_match_list</replaceable> } <optional> except-from { <replaceable>namelist</replaceable> } </optional>;</optional>
Adam Tkac 05cf27
     <optional> deny-answer-aliases { <replaceable>namelist</replaceable> } <optional> except-from { <replaceable>namelist</replaceable> } </optional>;</optional>
Adam Tkac 05cf27
+    <optional> rate-limit {
Adam Tkac 05cf27
+	<optional> responses-per-second <replaceable>number</replaceable> ; </optional>
Tomas Hozza df0cae
+	<optional> referrals-per-second <replaceable>number</replaceable> ; </optional>
Tomas Hozza df0cae
+	<optional> nodata-per-second <replaceable>number</replaceable> ; </optional>
Adam Tkac 05cf27
+	<optional> nxdomains-per-second <replaceable>number</replaceable> ; </optional>
Tomas Hozza df0cae
+	<optional> errors-per-second <replaceable>number</replaceable> ; </optional>
Adam Tkac 05cf27
+	<optional> all-per-second <replaceable>number</replaceable> ; </optional>
Adam Tkac 05cf27
+	<optional> window <replaceable>number</replaceable> ; </optional>
Adam Tkac 05cf27
+	<optional> log-only <replaceable>yes_or_no</replaceable> ; </optional>
Adam Tkac 05cf27
+	<optional> qps-scale <replaceable>number</replaceable> ; </optional>
Tomas Hozza df0cae
+	<optional> ipv4-prefix-length <replaceable>number</replaceable> ; </optional>
Tomas Hozza df0cae
+	<optional> ipv6-prefix-length <replaceable>number</replaceable> ; </optional>
Adam Tkac 05cf27
+	<optional> slip <replaceable>number</replaceable> ; </optional>
Adam Tkac 05cf27
+	<optional> exempt-clients  { <replaceable>address_match_list</replaceable> } ; </optional>
Adam Tkac 05cf27
+	<optional> max-table-size <replaceable>number</replaceable> ; </optional>
Adam Tkac 05cf27
+	<optional> min-table-size <replaceable>number</replaceable> ; </optional>
Adam Tkac 05cf27
+      } ; </optional>
Adam Tkac 05cf27
     <optional> response-policy { <replaceable>zone_name</replaceable>
Adam Tkac 05cf27
 	<optional> policy given | disabled | passthru | nxdomain | nodata | cname <replaceable>domain</replaceable> </optional>
Adam Tkac 05cf27
 	<optional> recursive-only <replaceable>yes_or_no</replaceable> </optional> <optional> max-policy-ttl <replaceable>number</replaceable> </optional> ;
Tomas Hozza df0cae
@@ -9897,6 +9940,223 @@
Adam Tkac c9b941
             <command>RPZRewrites</command> statistics.
Adam Tkac c9b941
           </para>
Adam Tkac 05cf27
         </sect3>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	<sect3>
Tomas Hozza df0cae
+	  <title>Response Rate Limiting</title>
Adam Tkac 05cf27
+	  <para>
Tomas Hozza df0cae
+	    Excessive almost-identical UDP <emphasis>responses</emphasis>
Tomas Hozza df0cae
+	    can be controlled by configuring a
Adam Tkac 05cf27
+	    <command>rate-limit</command> clause in an
Tomas Hozza df0cae
+	    <command>options</command> or <command>view</command> statement.
Tomas Hozza df0cae
+	    This mechanism keeps authoritative BIND 9 from being used
Tomas Hozza df0cae
+	    in amplifying reflection denial of service (DoS) attacks.
Tomas Hozza df0cae
+	    Short truncated (TC=1) responses can be sent to provide
Tomas Hozza df0cae
+	    rate-limited responses to legitimate clients within
Tomas Hozza df0cae
+	    a range of forged, attacked IP addresses.
Tomas Hozza df0cae
+	    Legitimate clients react to dropped or truncated response
Tomas Hozza df0cae
+	    by retrying with UDP or with TCP respectively.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Tomas Hozza df0cae
+	    This mechanism is intended for authoritative DNS servers.
Tomas Hozza df0cae
+	    It can be used on recursive servers but can slow
Tomas Hozza df0cae
+	    applications such as SMTP servers (mail receivers) and
Tomas Hozza df0cae
+	    HTTP clients (web browsers) that repeatedly request the
Tomas Hozza df0cae
+	    same domains.
Tomas Hozza df0cae
+	    When possible, closing "open" recursive servers is better.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Tomas Hozza df0cae
+	    Response rate limiting uses a "credit" or "token bucket" scheme.
Tomas Hozza df0cae
+	    Each combination of identical response and client
Tomas Hozza df0cae
+	    has a conceptual account that earns a specified number
Tomas Hozza df0cae
+	    of credits every second.
Tomas Hozza df0cae
+	    A prospective response debits its account by one.
Tomas Hozza df0cae
+	    Responses are dropped or truncated
Tomas Hozza df0cae
+	    while the account is negative.
Tomas Hozza df0cae
+            Responses are tracked within a rolling window of time
Tomas Hozza df0cae
+            which defaults to 15 seconds, but can be configured with
Tomas Hozza df0cae
+            the <command>window</command> option to any value from
Tomas Hozza df0cae
+            1 to 3600 seconds (1 hour).
Tomas Hozza df0cae
+	    The account cannot become more positive than
Tomas Hozza df0cae
+	    the per-second limit
Tomas Hozza df0cae
+	    or more negative than <command>window</command>
Tomas Hozza df0cae
+	    times the per-second limit.
Tomas Hozza df0cae
+            When the specified number of credits for a class of
Tomas Hozza df0cae
+            responses is set to 0, those responses are not rate limited.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Tomas Hozza df0cae
+	    The notions of "identical response" and "DNS client"
Tomas Hozza df0cae
+	    for rate limiting are not simplistic.
Tomas Hozza df0cae
+	    All responses to an address block are counted as if to a
Tomas Hozza df0cae
+	    single client.
Tomas Hozza df0cae
+	    The prefix lengths of addresses blocks are
Tomas Hozza df0cae
+	    specified with <command>ipv4-prefix-length</command> (default 24)
Tomas Hozza df0cae
+	    and <command>ipv6-prefix-length</command> (default 56).
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Tomas Hozza df0cae
+	    All non-empty responses for a valid domain name (qname)
Tomas Hozza df0cae
+	    and record type (qtype) are identical and have a limit specified
Tomas Hozza df0cae
+	    with <command>responses-per-second</command>
Tomas Hozza df0cae
+	    (default 0 or no limit).
Tomas Hozza df0cae
+	    All empty (NODATA) responses for a valid domain,
Tomas Hozza df0cae
+	    regardless of query type, are identical.
Tomas Hozza df0cae
+	    Responses in the NODATA class are limited by
Tomas Hozza df0cae
+	    <command>nodata-per-second</command>
Tomas Hozza df0cae
+	    (default <command>responses-per-second</command>).
Tomas Hozza df0cae
+	    Requests for any and all undefined subdomains of a given
Tomas Hozza df0cae
+            valid domain result in NXDOMAIN errors, and are identical
Tomas Hozza df0cae
+            regardless of query type.
Tomas Hozza df0cae
+	    They are limited by <command>nxdomain-per-second</command>
Tomas Hozza df0cae
+	    (default <command>responses-per-second</command>).
Tomas Hozza df0cae
+	    This controls some attacks using random names, but
Tomas Hozza df0cae
+	    can be relaxed or turned off (set to 0)
Tomas Hozza df0cae
+	    on servers that expect many legitimate
Tomas Hozza df0cae
+	    NXDOMAIN responses, such as from anti-spam blacklists.
Tomas Hozza df0cae
+	    Referrals or delegations to the server of a given
Tomas Hozza df0cae
+	    domain are identical and are limited by
Tomas Hozza df0cae
+	    <command>referrals-per-second</command>
Tomas Hozza df0cae
+	    (default <command>responses-per-second</command>).
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    Responses generated from local wildcards are counted and limited
Adam Tkac 05cf27
+	    as if they were for the parent domain name.
Tomas Hozza df0cae
+	    This controls flooding using random.wild.example.com.
Tomas Hozza df0cae
+	  </para>
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+	  <para>
Tomas Hozza df0cae
+            All requests that result in DNS errors other
Tomas Hozza df0cae
+	    than NXDOMAIN, such as SERVFAIL and FORMERR, are identical
Tomas Hozza df0cae
+            regardless of requested name (qname) or record type (qtype).
Tomas Hozza df0cae
+	    This controls attacks using invalid requests or distant,
Tomas Hozza df0cae
+	    broken authoritative servers.
Tomas Hozza df0cae
+	    By default the limit on errors is the same as the
Tomas Hozza df0cae
+	    <command>responses-per-second</command> value,
Tomas Hozza df0cae
+	    but it can be set separately with
Tomas Hozza df0cae
+	    <command>errors-per-second</command>.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    Many attacks using DNS involve UDP requests with forged source
Adam Tkac 05cf27
+	    addresses.
Adam Tkac 05cf27
+	    Rate limiting prevents the use of BIND 9 to flood a network
Adam Tkac 05cf27
+	    with responses to requests with forged source addresses,
Adam Tkac 05cf27
+	    but could let a third party block responses to legitimate requests.
Adam Tkac 05cf27
+	    There is a mechanism that can answer some legitimate
Adam Tkac 05cf27
+	    requests from a client whose address is being forged in a flood.
Adam Tkac 05cf27
+	    Setting <command>slip</command> to 2 (its default) causes every
Tomas Hozza df0cae
+	    other UDP request to be answered with a small truncated (TC=1)
Tomas Hozza df0cae
+	    response.
Tomas Hozza df0cae
+	    The small size and reduced frequency, and so lack of
Tomas Hozza df0cae
+	    amplification, of "slipped" responses make them unattractive
Tomas Hozza df0cae
+	    for reflection DoS attacks.
Tomas Hozza df0cae
+	    <command>slip</command> must be between 0 and 10.
Tomas Hozza df0cae
+	    A value of 0 does not "slip";
Tomas Hozza df0cae
+	    no truncated responses are sent due to rate limiting.
Tomas Hozza df0cae
+	    Some error responses including REFUSED and SERVFAIL
Adam Tkac 05cf27
+	    cannot be replaced with truncated responses and are instead
Adam Tkac 05cf27
+	    leaked at the <command>slip</command> rate.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    When the approximate query per second rate exceeds
Adam Tkac 05cf27
+	    the <command>qps-scale</command> value,
Adam Tkac 05cf27
+	    then the <command>responses-per-second</command>,
Adam Tkac 05cf27
+	    <command>errors-per-second</command>,
Adam Tkac 05cf27
+	    <command>nxdomains-per-second</command> and
Adam Tkac 05cf27
+	    <command>all-per-second</command> values are reduced by the
Adam Tkac 05cf27
+	    ratio of the current rate to the <command>qps-scale</command> value.
Adam Tkac 05cf27
+	    This feature can tighten defenses during attacks.
Adam Tkac 05cf27
+	    For example, with
Adam Tkac 05cf27
+	    <command>qps-scale 250; responses-per-second 20;</command> and
Adam Tkac 05cf27
+	    a total query rate of 1000 queries/second for all queries from
Adam Tkac 05cf27
+	    all DNS clients including via TCP,
Adam Tkac 05cf27
+	    then the effective responses/second limit changes to
Adam Tkac 05cf27
+	    (250/1000)*20 or 5.
Adam Tkac 05cf27
+	    Responses sent via TCP are not limited
Adam Tkac 05cf27
+	    but are counted to compute the query per second rate.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    Communities of DNS clients can be given their own parameters or no
Adam Tkac 05cf27
+	    rate limiting by putting
Adam Tkac 05cf27
+	    <command>rate-limit</command> statements in <command>view</command>
Adam Tkac 05cf27
+	    statements instead of the global <command>option</command>
Adam Tkac 05cf27
+	    statement.
Tomas Hozza df0cae
+	    A <command>rate-limit</command> statement in a view replaces,
Tomas Hozza df0cae
+	    rather than supplementing, a <command>rate-limit</command>
Adam Tkac 05cf27
+	    statement among the main options.
Adam Tkac 05cf27
+	    DNS clients within a view can be exempted from rate limits
Adam Tkac 05cf27
+	    with the <command>exempt-clients</command> clause.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    UDP responses of all kinds can be limited with the
Adam Tkac 05cf27
+	    <command>all-per-second</command> phrase.
Adam Tkac 05cf27
+	    This rate limiting is unlike the rate limiting provided by
Adam Tkac 05cf27
+	    <command>responses-per-second</command>,
Adam Tkac 05cf27
+	    <command>errors-per-second</command>, and
Adam Tkac 05cf27
+	    <command>nxdomains-per-second</command> on a DNS server
Adam Tkac 05cf27
+	    which are often invisible to the victim of a DNS reflection attack.
Adam Tkac 05cf27
+	    Unless the forged requests of the attack are the same as the
Adam Tkac 05cf27
+	    legitimate requests of the victim, the victim's requests are
Adam Tkac 05cf27
+	    not affected.
Adam Tkac 05cf27
+	    Responses affected by an <command>all-per-second</command> limit
Adam Tkac 05cf27
+	    are always dropped; the <command>slip</command> value has no
Adam Tkac 05cf27
+	    effect.
Adam Tkac 05cf27
+	    An <command>all-per-second</command> limit should be
Adam Tkac 05cf27
+	    at least 4 times as large as the other limits,
Adam Tkac 05cf27
+	    because single DNS clients often send bursts of legitimate
Adam Tkac 05cf27
+	    requests.
Adam Tkac 05cf27
+	    For example, the receipt of a single mail message can prompt
Adam Tkac 05cf27
+	    requests from an SMTP server for NS, PTR, A, and AAAA records
Adam Tkac 05cf27
+	    as the incoming SMTP/TCP/IP connection is considered.
Adam Tkac 05cf27
+	    The SMTP server can need additional NS, A, AAAA, MX, TXT, and SPF
Adam Tkac 05cf27
+	    records as it considers the STMP <command>Mail From</command>
Adam Tkac 05cf27
+	    command.
Adam Tkac 05cf27
+	    Web browsers often repeatedly resolve the same names that
Adam Tkac 05cf27
+	    are repeated in HTML <IMG> tags in a page.
Adam Tkac 05cf27
+	    <command>All-per-second</command> is similar to the
Adam Tkac 05cf27
+	    rate limiting offered by firewalls but often inferior.
Adam Tkac 05cf27
+	    Attacks that justify ignoring the
Adam Tkac 05cf27
+	    contents of DNS responses are likely to be attacks on the
Adam Tkac 05cf27
+	    DNS server itself.
Adam Tkac 05cf27
+	    They usually should be discarded before the DNS server
Adam Tkac 05cf27
+	    spends resources make TCP connections or parsing DNS requesets,
Adam Tkac 05cf27
+	    but that rate limiting must be done before the
Adam Tkac 05cf27
+	    DNS server sees the requests.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    The maximum size of the table used to track requests and
Adam Tkac 05cf27
+	    rate limit responses is set with <command>max-table-size</command>.
Adam Tkac 05cf27
+	    Each entry in the table is between 40 and 80 bytes.
Adam Tkac 05cf27
+	    The table needs approximately as many entries as the number
Adam Tkac 05cf27
+	    of requests received per second.
Adam Tkac 05cf27
+	    The default is 20,000.
Adam Tkac 05cf27
+	    To reduce the cold start of growing the table,
Adam Tkac 05cf27
+	    <command>min-table-size</command> (default 500)
Adam Tkac 05cf27
+	    can set the minimum table size.
Adam Tkac 05cf27
+	    Enable <command>rate-limit</command> category logging to monitor
Adam Tkac 05cf27
+	    expansions of the table and inform
Adam Tkac 05cf27
+	    choices for the initial and maximum table size.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    Use <command>log-only yes</command> to test rate limiting parameters
Adam Tkac 05cf27
+	    without actually dropping any requests.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    Responses dropped by rate limits are included in the
Adam Tkac 05cf27
+	    <command>RateDropped</command> and <command>QryDropped</command>
Adam Tkac 05cf27
+	    statistics.
Adam Tkac 05cf27
+	    Responses that truncated by rate limits are included in
Adam Tkac 05cf27
+	    <command>RateSlipped</command> and <command>RespTruncated</command>.
Adam Tkac 05cf27
+	</sect3>
Adam Tkac 05cf27
       </sect2>
Adam Tkac 05cf27
 
Adam Tkac 05cf27
       <sect2 id="server_statement_grammar">
Tomas Hozza df0cae
@@ -14649,6 +14909,32 @@
Adam Tkac 05cf27
 		      </para>
Adam Tkac 05cf27
 		    </entry>
Adam Tkac 05cf27
 		  </row>
Adam Tkac 05cf27
+		  <row rowsep="0">
Adam Tkac 05cf27
+		    <entry colname="1">
Adam Tkac 05cf27
+		      <para><command>RateDropped</command></para>
Adam Tkac 05cf27
+		    </entry>
Adam Tkac 05cf27
+		    <entry colname="2">
Adam Tkac 05cf27
+		      <para><command></command></para>
Adam Tkac 05cf27
+		    </entry>
Adam Tkac 05cf27
+		    <entry colname="3">
Adam Tkac 05cf27
+		      <para>
Adam Tkac 05cf27
+			Responses dropped by rate limits.
Adam Tkac 05cf27
+		      </para>
Adam Tkac 05cf27
+		    </entry>
Adam Tkac 05cf27
+		  </row>
Adam Tkac 05cf27
+		  <row rowsep="0">
Adam Tkac 05cf27
+		    <entry colname="1">
Adam Tkac 05cf27
+		      <para><command>RateSlipped</command></para>
Adam Tkac 05cf27
+		    </entry>
Adam Tkac 05cf27
+		    <entry colname="2">
Adam Tkac 05cf27
+		      <para><command></command></para>
Adam Tkac 05cf27
+		    </entry>
Adam Tkac 05cf27
+		    <entry colname="3">
Adam Tkac 05cf27
+		      <para>
Adam Tkac 05cf27
+			Responses truncated by rate limits.
Adam Tkac 05cf27
+		      </para>
Adam Tkac 05cf27
+		    </entry>
Adam Tkac 05cf27
+		  </row>
Adam Tkac 05cf27
 		
Adam Tkac 05cf27
               </tgroup>
Adam Tkac 05cf27
             </informaltable>
Tomas Hozza df0cae
diff -r -u lib/dns/Makefile.in-orig lib/dns/Makefile.in
Tomas Hozza df0cae
--- lib/dns/Makefile.in-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ lib/dns/Makefile.in	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -67,8 +67,8 @@
Tomas Hozza df0cae
 		portlist.@O@ private.@O@ \
Tomas Hozza df0cae
 		rbt.@O@ rbtdb.@O@ rbtdb64.@O@ rcode.@O@ rdata.@O@ \
Tomas Hozza df0cae
 		rdatalist.@O@ rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ \
Tomas Hozza df0cae
-		request.@O@ resolver.@O@ result.@O@ rootns.@O@ rpz.@O@ \
Tomas Hozza df0cae
-		rriterator.@O@ sdb.@O@ \
Tomas Hozza df0cae
+		request.@O@ resolver.@O@ result.@O@ rootns.@O@ \
Tomas Hozza df0cae
+		rpz.@O@ rrl.@O@ rriterator.@O@ sdb.@O@ \
Tomas Hozza df0cae
 		sdlz.@O@ soa.@O@ ssu.@O@ ssu_external.@O@ \
Tomas Hozza df0cae
 		stats.@O@ tcpmsg.@O@ time.@O@ timer.@O@ tkey.@O@ \
Tomas Hozza df0cae
 		tsec.@O@ tsig.@O@ ttl.@O@ update.@O@ validator.@O@ \
Tomas Hozza df0cae
@@ -95,7 +95,7 @@
Tomas Hozza df0cae
 		name.c ncache.c nsec.c nsec3.c order.c peer.c portlist.c \
Tomas Hozza df0cae
 		rbt.c rbtdb.c rbtdb64.c rcode.c rdata.c rdatalist.c \
Tomas Hozza df0cae
 		rdataset.c rdatasetiter.c rdataslab.c request.c \
Tomas Hozza df0cae
-		resolver.c result.c rootns.c rpz.c rriterator.c \
Tomas Hozza df0cae
+		resolver.c result.c rootns.c rpz.c rrl.c rriterator.c \
Tomas Hozza df0cae
 		sdb.c sdlz.c soa.c ssu.c ssu_external.c \
Tomas Hozza df0cae
 		stats.c tcpmsg.c time.c timer.c tkey.c \
Tomas Hozza df0cae
 		tsec.c tsig.c ttl.c update.c validator.c \
Tomas Hozza df0cae
diff -r -u lib/dns/include/dns/log.h-orig lib/dns/include/dns/log.h
Tomas Hozza df0cae
--- lib/dns/include/dns/log.h-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ lib/dns/include/dns/log.h	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -43,6 +43,7 @@
Adam Tkac 05cf27
 #define DNS_LOGCATEGORY_DELEGATION_ONLY	(&dns_categories[10])
Adam Tkac 05cf27
 #define DNS_LOGCATEGORY_EDNS_DISABLED	(&dns_categories[11])
Adam Tkac 05cf27
 #define DNS_LOGCATEGORY_RPZ		(&dns_categories[12])
Adam Tkac 05cf27
+#define DNS_LOGCATEGORY_RRL		(&dns_categories[13])
Adam Tkac 05cf27
 
Adam Tkac 05cf27
 /* Backwards compatibility. */
Adam Tkac 05cf27
 #define DNS_LOGCATEGORY_GENERAL		ISC_LOGCATEGORY_GENERAL
Tomas Hozza df0cae
diff -r -u lib/dns/include/dns/rrl.h-orig lib/dns/include/dns/rrl.h
Tomas Hozza df0cae
--- lib/dns/include/dns/rrl.h-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ lib/dns/include/dns/rrl.h	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -0,0 +1,278 @@
Adam Tkac 05cf27
+/*
Adam Tkac c9b941
+ * Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+ *
Adam Tkac 05cf27
+ * Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+ * purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+ * copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+ *
Adam Tkac 05cf27
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+ * PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#ifndef DNS_RRL_H
Adam Tkac 05cf27
+#define DNS_RRL_H 1
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Rate limit DNS responses.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#include <isc/lang.h>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#include <dns/fixedname.h>
Adam Tkac 05cf27
+#include <dns/rdata.h>
Adam Tkac 05cf27
+#include <dns/types.h>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+ISC_LANG_BEGINDECLS
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Memory allocation or other failures.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+#define DNS_RRL_LOG_FAIL	ISC_LOG_WARNING
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * dropped or slipped responses.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+#define DNS_RRL_LOG_DROP	ISC_LOG_INFO
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Major events in dropping or slipping.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+#define DNS_RRL_LOG_DEBUG1	ISC_LOG_DEBUG(3)
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Limit computations.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+#define DNS_RRL_LOG_DEBUG2	ISC_LOG_DEBUG(4)
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Even less interesting.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+#define DNS_RRL_LOG_DEBUG3	ISC_LOG_DEBUG(9)
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#define DNS_RRL_LOG_ERR_LEN	64
Adam Tkac 05cf27
+#define DNS_RRL_LOG_BUF_LEN	(sizeof("would continue limiting") +	\
Adam Tkac 05cf27
+				 DNS_RRL_LOG_ERR_LEN +			\
Adam Tkac 05cf27
+				 sizeof(" responses to ") +		\
Adam Tkac 05cf27
+				 ISC_NETADDR_FORMATSIZE +		\
Adam Tkac 05cf27
+				 sizeof("/128 for IN ") +		\
Adam Tkac 05cf27
+				 DNS_RDATATYPE_FORMATSIZE +		\
Adam Tkac 05cf27
+				 DNS_NAME_FORMATSIZE)
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+typedef struct dns_rrl_hash dns_rrl_hash_t;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Response types.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+typedef enum {
Adam Tkac 05cf27
+	DNS_RRL_RTYPE_FREE = 0,
Adam Tkac 05cf27
+	DNS_RRL_RTYPE_QUERY,
Tomas Hozza df0cae
+	DNS_RRL_RTYPE_REFERRAL,
Tomas Hozza df0cae
+	DNS_RRL_RTYPE_NODATA,
Adam Tkac 05cf27
+	DNS_RRL_RTYPE_NXDOMAIN,
Adam Tkac 05cf27
+	DNS_RRL_RTYPE_ERROR,
Adam Tkac 05cf27
+	DNS_RRL_RTYPE_ALL,
Adam Tkac 05cf27
+	DNS_RRL_RTYPE_TCP,
Adam Tkac 05cf27
+} dns_rrl_rtype_t;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * A rate limit bucket key.
Adam Tkac 05cf27
+ * This should be small to limit the total size of the database.
Adam Tkac 05cf27
+ * The hash of the qname should be wide enough to make the probability
Adam Tkac 05cf27
+ * of collisions among requests from a single IP address block less than 50%.
Adam Tkac 05cf27
+ * We need a 32-bit hash value for 10000 qps (e.g. random qnames forged
Adam Tkac 05cf27
+ * by attacker) to collide with legitimate qnames from the target with
Adam Tkac 05cf27
+ * probability at most 1%.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+#define DNS_RRL_MAX_PREFIX  64
Adam Tkac 05cf27
+typedef union dns_rrl_key dns_rrl_key_t;
Adam Tkac 05cf27
+union dns_rrl_key {
Adam Tkac 05cf27
+	struct {
Adam Tkac 05cf27
+		isc_uint32_t	    ip[DNS_RRL_MAX_PREFIX/32];
Adam Tkac 05cf27
+		isc_uint32_t	    qname_hash;
Adam Tkac 05cf27
+		dns_rdatatype_t	    qtype;
Adam Tkac 05cf27
+		isc_uint8_t	    qclass;
Adam Tkac c9b941
+		dns_rrl_rtype_t	    rtype   :4; /* 3 bits + sign bit */
Adam Tkac 05cf27
+		isc_boolean_t	    ipv6    :1;
Adam Tkac 05cf27
+	} s;
Adam Tkac 05cf27
+	isc_uint16_t	w[1];
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * A rate-limit entry.
Adam Tkac 05cf27
+ * This should be small to limit the total size of the table of entries.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+typedef struct dns_rrl_entry dns_rrl_entry_t;
Adam Tkac 05cf27
+typedef ISC_LIST(dns_rrl_entry_t) dns_rrl_bin_t;
Adam Tkac 05cf27
+struct dns_rrl_entry {
Adam Tkac 05cf27
+	ISC_LINK(dns_rrl_entry_t) lru;
Adam Tkac 05cf27
+	ISC_LINK(dns_rrl_entry_t) hlink;
Adam Tkac 05cf27
+	dns_rrl_key_t	key;
Adam Tkac 05cf27
+# define DNS_RRL_RESPONSE_BITS	24
Adam Tkac 05cf27
+	signed int	responses   :DNS_RRL_RESPONSE_BITS;
Adam Tkac 05cf27
+# define DNS_RRL_QNAMES_BITS	8
Adam Tkac 05cf27
+	unsigned int	log_qname   :DNS_RRL_QNAMES_BITS;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# define DNS_RRL_TS_GEN_BITS	2
Adam Tkac 05cf27
+	unsigned int	ts_gen	    :DNS_RRL_TS_GEN_BITS;
Adam Tkac 05cf27
+	isc_boolean_t	ts_valid    :1;
Adam Tkac 05cf27
+# define DNS_RRL_HASH_GEN_BITS	1
Adam Tkac 05cf27
+	unsigned int	hash_gen    :DNS_RRL_HASH_GEN_BITS;
Adam Tkac 05cf27
+	isc_boolean_t	logged	    :1;
Adam Tkac 05cf27
+# define DNS_RRL_LOG_BITS	11
Adam Tkac 05cf27
+	unsigned int	log_secs    :DNS_RRL_LOG_BITS;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# define DNS_RRL_TS_BITS	12
Adam Tkac 05cf27
+	unsigned int	ts	    :DNS_RRL_TS_BITS;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# define DNS_RRL_MAX_SLIP	10
Adam Tkac 05cf27
+	unsigned int	slip_cnt    :4;
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#define DNS_RRL_MAX_TIME_TRAVEL	5
Adam Tkac 05cf27
+#define DNS_RRL_FOREVER		(1<
Adam Tkac 05cf27
+#define DNS_RRL_MAX_TS		(DNS_RRL_FOREVER - 1)
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#define DNS_RRL_MAX_RESPONSES	((1<<(DNS_RRL_RESPONSE_BITS-1))-1)
Adam Tkac 05cf27
+#define DNS_RRL_MAX_WINDOW	3600
Adam Tkac 05cf27
+#if DNS_RRL_MAX_WINDOW >= DNS_RRL_MAX_TS
Adam Tkac 05cf27
+#error "DNS_RRL_MAX_WINDOW is too large"
Adam Tkac 05cf27
+#endif
Adam Tkac 05cf27
+#define DNS_RRL_MAX_RATE	1000
Adam Tkac 05cf27
+#if DNS_RRL_MAX_RATE >= (DNS_RRL_MAX_RESPONSES / DNS_RRL_MAX_WINDOW)
Adam Tkac 05cf27
+#error "DNS_RRL_MAX_rate is too large"
Adam Tkac 05cf27
+#endif
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#if (1<<DNS_RRL_LOG_BITS) >= DNS_RRL_FOREVER
Adam Tkac 05cf27
+#error DNS_RRL_LOG_BITS is too big
Adam Tkac 05cf27
+#endif
Adam Tkac 05cf27
+#define DNS_RRL_MAX_LOG_SECS	1800
Adam Tkac 05cf27
+#if DNS_RRL_MAX_LOG_SECS >= (1<
Adam Tkac 05cf27
+#error "DNS_RRL_MAX_LOG_SECS is too large"
Adam Tkac 05cf27
+#endif
Adam Tkac 05cf27
+#define DNS_RRL_STOP_LOG_SECS	60
Adam Tkac 05cf27
+#if DNS_RRL_STOP_LOG_SECS >= (1<
Adam Tkac 05cf27
+#error "DNS_RRL_STOP_LOG_SECS is too large"
Adam Tkac 05cf27
+#endif
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * A hash table of rate-limit entries.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+struct dns_rrl_hash {
Adam Tkac 05cf27
+	isc_stdtime_t	check_time;
Adam Tkac 05cf27
+	unsigned int	gen	    :DNS_RRL_HASH_GEN_BITS;
Adam Tkac 05cf27
+	int		length;
Adam Tkac 05cf27
+	dns_rrl_bin_t	bins[1];
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * A block of rate-limit entries.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+typedef struct dns_rrl_block dns_rrl_block_t;
Adam Tkac 05cf27
+struct dns_rrl_block {
Adam Tkac 05cf27
+	ISC_LINK(dns_rrl_block_t) link;
Adam Tkac 05cf27
+	int		size;
Adam Tkac 05cf27
+	dns_rrl_entry_t	entries[1];
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * A rate limited qname buffer.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+typedef struct dns_rrl_qname_buf dns_rrl_qname_buf_t;
Adam Tkac 05cf27
+struct dns_rrl_qname_buf {
Adam Tkac 05cf27
+	ISC_LINK(dns_rrl_qname_buf_t) link;
Adam Tkac 05cf27
+	const dns_rrl_entry_t *e;
Adam Tkac 05cf27
+	unsigned int	    index;
Adam Tkac 05cf27
+	dns_fixedname_t	    qname;
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+typedef struct dns_rrl_rate dns_rrl_rate_t;
Tomas Hozza df0cae
+struct dns_rrl_rate {
Tomas Hozza df0cae
+	int	    r;
Tomas Hozza df0cae
+	int	    scaled;
Tomas Hozza df0cae
+	const char  *str;
Tomas Hozza df0cae
+};
Tomas Hozza df0cae
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Per-view query rate limit parameters and a pointer to database.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+typedef struct dns_rrl dns_rrl_t;
Adam Tkac 05cf27
+struct dns_rrl {
Adam Tkac 05cf27
+	isc_mutex_t	lock;
Adam Tkac 05cf27
+	isc_mem_t	*mctx;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	isc_boolean_t	log_only;
Tomas Hozza df0cae
+	dns_rrl_rate_t	responses_per_second;
Tomas Hozza df0cae
+	dns_rrl_rate_t	referrals_per_second;
Tomas Hozza df0cae
+	dns_rrl_rate_t	nodata_per_second;
Tomas Hozza df0cae
+	dns_rrl_rate_t	nxdomains_per_second;
Tomas Hozza df0cae
+	dns_rrl_rate_t	errors_per_second;
Tomas Hozza df0cae
+	dns_rrl_rate_t	all_per_second;
Tomas Hozza df0cae
+	dns_rrl_rate_t	slip;
Adam Tkac 05cf27
+	int		window;
Adam Tkac 05cf27
+	double		qps_scale;
Adam Tkac 05cf27
+	int		max_entries;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	dns_acl_t	*exempt;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	int		num_entries;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	int		qps_responses;
Adam Tkac 05cf27
+	isc_stdtime_t	qps_time;
Adam Tkac 05cf27
+	double		qps;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	unsigned int	probes;
Adam Tkac 05cf27
+	unsigned int	searches;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	ISC_LIST(dns_rrl_block_t) blocks;
Adam Tkac 05cf27
+	ISC_LIST(dns_rrl_entry_t) lru;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	dns_rrl_hash_t	*hash;
Adam Tkac 05cf27
+	dns_rrl_hash_t	*old_hash;
Adam Tkac 05cf27
+	unsigned int	hash_gen;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	unsigned int	ts_gen;
Adam Tkac 05cf27
+# define DNS_RRL_TS_BASES   (1<
Adam Tkac 05cf27
+	isc_stdtime_t	ts_bases[DNS_RRL_TS_BASES];
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	int		ipv4_prefixlen;
Adam Tkac 05cf27
+	isc_uint32_t	ipv4_mask;
Adam Tkac 05cf27
+	int		ipv6_prefixlen;
Adam Tkac 05cf27
+	isc_uint32_t	ipv6_mask[4];
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	isc_stdtime_t	log_stops_time;
Adam Tkac 05cf27
+	dns_rrl_entry_t	*last_logged;
Adam Tkac 05cf27
+	int		num_logged;
Adam Tkac 05cf27
+	int		num_qnames;
Adam Tkac 05cf27
+	ISC_LIST(dns_rrl_qname_buf_t) qname_free;
Adam Tkac 05cf27
+# define DNS_RRL_QNAMES	    (1<
Adam Tkac 05cf27
+	dns_rrl_qname_buf_t *qnames[DNS_RRL_QNAMES];
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+typedef enum {
Adam Tkac 05cf27
+	DNS_RRL_RESULT_OK,
Adam Tkac 05cf27
+	DNS_RRL_RESULT_DROP,
Adam Tkac 05cf27
+	DNS_RRL_RESULT_SLIP,
Adam Tkac 05cf27
+} dns_rrl_result_t;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+dns_rrl_result_t
Adam Tkac 05cf27
+dns_rrl(dns_view_t *view,
Adam Tkac 05cf27
+	const isc_sockaddr_t *client_addr, isc_boolean_t is_tcp,
Adam Tkac 05cf27
+	dns_rdataclass_t rdclass, dns_rdatatype_t qtype,
Adam Tkac 05cf27
+	dns_name_t *qname, isc_result_t resp_result, isc_stdtime_t now,
Adam Tkac 05cf27
+	isc_boolean_t wouldlog, char *log_buf, unsigned int log_buf_len);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+void
Adam Tkac 05cf27
+dns_rrl_view_destroy(dns_view_t *view);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+isc_result_t
Adam Tkac 05cf27
+dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+ISC_LANG_ENDDECLS
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#endif /* DNS_RRL_H */
Tomas Hozza df0cae
diff -r -u lib/dns/include/dns/view.h-orig lib/dns/include/dns/view.h
Tomas Hozza df0cae
--- lib/dns/include/dns/view.h-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ lib/dns/include/dns/view.h	2004-01-01 00:00:00.000000000 +0000
Adam Tkac 05cf27
@@ -73,6 +73,7 @@
Adam Tkac 05cf27
 
Adam Tkac 05cf27
 #include <dns/acl.h>
Adam Tkac 05cf27
 #include <dns/fixedname.h>
Adam Tkac 05cf27
+#include <dns/rrl.h>
Adam Tkac 05cf27
 #include <dns/rdatastruct.h>
Adam Tkac 05cf27
 #include <dns/rpz.h>
Adam Tkac 05cf27
 #include <dns/types.h>
Tomas Hozza df0cae
@@ -142,6 +143,7 @@
Adam Tkac 05cf27
 	dns_rbt_t *			answeracl_exclude;
Adam Tkac 05cf27
 	dns_rbt_t *			denyanswernames;
Adam Tkac 05cf27
 	dns_rbt_t *			answernames_exclude;
Adam Tkac 05cf27
+	dns_rrl_t *			rrl;
Adam Tkac 05cf27
 	isc_boolean_t			provideixfr;
Adam Tkac 05cf27
 	isc_boolean_t			requestnsid;
Adam Tkac 05cf27
 	dns_ttl_t			maxcachettl;
Tomas Hozza df0cae
diff -r -u lib/dns/log.c-orig lib/dns/log.c
Tomas Hozza df0cae
--- lib/dns/log.c-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ lib/dns/log.c	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -45,6 +45,7 @@
Adam Tkac 05cf27
 	{ "delegation-only", 0 },
Adam Tkac 05cf27
 	{ "edns-disabled", 0 },
Adam Tkac 05cf27
 	{ "rpz",	0 },
Adam Tkac 05cf27
+	{ "rate-limit",	0 },
Adam Tkac 05cf27
 	{ NULL, 	0 }
Adam Tkac 05cf27
 };
Adam Tkac 05cf27
 
Tomas Hozza df0cae
diff -r -u lib/dns/rrl.c-orig lib/dns/rrl.c
Tomas Hozza df0cae
--- lib/dns/rrl.c-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ lib/dns/rrl.c	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -0,0 +1,1324 @@
Adam Tkac 05cf27
+/*
Adam Tkac c9b941
+ * Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
Adam Tkac 05cf27
+ *
Adam Tkac 05cf27
+ * Permission to use, copy, modify, and/or distribute this software for any
Adam Tkac 05cf27
+ * purpose with or without fee is hereby granted, provided that the above
Adam Tkac 05cf27
+ * copyright notice and this permission notice appear in all copies.
Adam Tkac 05cf27
+ *
Adam Tkac 05cf27
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
Adam Tkac 05cf27
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
Adam Tkac 05cf27
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
Adam Tkac 05cf27
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
Adam Tkac 05cf27
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
Adam Tkac 05cf27
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
Adam Tkac 05cf27
+ * PERFORMANCE OF THIS SOFTWARE.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*! \file */
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Rate limit DNS responses.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/* #define ISC_LIST_CHECKINIT */
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#include <config.h>
Adam Tkac 05cf27
+#include <isc/mem.h>
Adam Tkac 05cf27
+#include <isc/net.h>
Adam Tkac 05cf27
+#include <isc/netaddr.h>
Adam Tkac c9b941
+#include <isc/print.h>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#include <dns/result.h>
Adam Tkac 05cf27
+#include <dns/rcode.h>
Adam Tkac 05cf27
+#include <dns/rdatatype.h>
Adam Tkac 05cf27
+#include <dns/rdataclass.h>
Adam Tkac 05cf27
+#include <dns/log.h>
Adam Tkac 05cf27
+#include <dns/rrl.h>
Adam Tkac 05cf27
+#include <dns/view.h>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static void
Adam Tkac 05cf27
+log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_boolean_t early,
Adam Tkac 05cf27
+	char *log_buf, unsigned int log_buf_len);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Get a modulus for a hash function that is tolerably likely to be
Adam Tkac 05cf27
+ * relatively prime to most inputs.  Of course, we get a prime for for initial
Adam Tkac 05cf27
+ * values not larger than the square of the last prime.  We often get a prime
Adam Tkac 05cf27
+ * after that.
Adam Tkac 05cf27
+ * This works well in practice for hash tables up to at least 100
Adam Tkac 05cf27
+ * times the square of the last prime and better than a multiplicative hash.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+static int
Adam Tkac 05cf27
+hash_divisor(unsigned int initial) {
Adam Tkac 05cf27
+	static isc_uint16_t primes[] = {
Adam Tkac 05cf27
+		  3,   5,   7,  11,  13,  17,  19,  23,  29,  31,  37,  41,
Adam Tkac 05cf27
+		 43,  47,  53,  59,  61,  67,  71,  73,  79,  83,  89,  97,
Adam Tkac 05cf27
+#if 0
Adam Tkac 05cf27
+		101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157,
Adam Tkac 05cf27
+		163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227,
Adam Tkac 05cf27
+		229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283,
Adam Tkac 05cf27
+		293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367,
Adam Tkac 05cf27
+		373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439,
Adam Tkac 05cf27
+		443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509,
Adam Tkac 05cf27
+		521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599,
Adam Tkac 05cf27
+		601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661,
Adam Tkac 05cf27
+		673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751,
Adam Tkac 05cf27
+		757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829,
Adam Tkac 05cf27
+		839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919,
Adam Tkac 05cf27
+		929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997,1009,
Adam Tkac 05cf27
+#endif
Adam Tkac 05cf27
+	};
Adam Tkac 05cf27
+	int divisions, tries;
Adam Tkac 05cf27
+	unsigned int result;
Adam Tkac 05cf27
+	isc_uint16_t *pp, p;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	result = initial;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (primes[sizeof(primes)/sizeof(primes[0])-1] >= result) {
Adam Tkac 05cf27
+		pp = primes;
Adam Tkac 05cf27
+		while (*pp < result)
Adam Tkac 05cf27
+			++pp;
Adam Tkac 05cf27
+		return (*pp);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if ((result & 1) == 0)
Adam Tkac 05cf27
+		++result;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	divisions = 0;
Adam Tkac 05cf27
+	tries = 1;
Adam Tkac 05cf27
+	pp = primes;
Adam Tkac 05cf27
+	do {
Adam Tkac 05cf27
+		p = *pp++;
Adam Tkac 05cf27
+		++divisions;
Adam Tkac 05cf27
+		if ((result % p) == 0) {
Adam Tkac 05cf27
+			++tries;
Adam Tkac 05cf27
+			result += 2;
Adam Tkac 05cf27
+			pp = primes;
Adam Tkac 05cf27
+		}
Adam Tkac c9b941
+	} while (pp < &primes[sizeof(primes) / sizeof(primes[0])]);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3))
Adam Tkac 05cf27
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG3,
Adam Tkac 05cf27
+			      "%d hash_divisor() divisions in %d tries"
Adam Tkac 05cf27
+			      " to get %d from %d",
Adam Tkac 05cf27
+			      divisions, tries, result, initial);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	return (result);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Convert a timestamp to a number of seconds in the past.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+static inline int
Adam Tkac 05cf27
+delta_rrl_time(isc_stdtime_t ts, isc_stdtime_t now) {
Adam Tkac 05cf27
+	int delta;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	delta = now - ts;
Adam Tkac 05cf27
+	if (delta >= 0)
Adam Tkac 05cf27
+		return (delta);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * The timestamp is in the future.  That future might result from
Adam Tkac 05cf27
+	 * re-ordered requests, because we use timestamps on requests
Adam Tkac 05cf27
+	 * instead of consulting a clock.  Timestamps in the distant future are
Adam Tkac 05cf27
+	 * assumed to result from clock changes.  When the clock changes to
Adam Tkac 05cf27
+	 * the past, make existing timestamps appear to be in the past.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (delta < -DNS_RRL_MAX_TIME_TRAVEL)
Adam Tkac 05cf27
+		return (DNS_RRL_FOREVER);
Adam Tkac 05cf27
+	return (0);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static inline int
Adam Tkac 05cf27
+get_age(const dns_rrl_t *rrl, const dns_rrl_entry_t *e, isc_stdtime_t now) {
Adam Tkac 05cf27
+	if (!e->ts_valid)
Adam Tkac 05cf27
+		return (DNS_RRL_FOREVER);
Adam Tkac 05cf27
+	return (delta_rrl_time(e->ts + rrl->ts_bases[e->ts_gen], now));
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static inline void
Adam Tkac 05cf27
+set_age(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_stdtime_t now) {
Adam Tkac 05cf27
+	dns_rrl_entry_t *e_old;
Adam Tkac 05cf27
+	unsigned int ts_gen;
Adam Tkac 05cf27
+	int i, ts;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	ts_gen = rrl->ts_gen;
Adam Tkac 05cf27
+	ts = now - rrl->ts_bases[ts_gen];
Adam Tkac 05cf27
+	if (ts < 0) {
Adam Tkac 05cf27
+		if (ts < -DNS_RRL_MAX_TIME_TRAVEL)
Adam Tkac 05cf27
+			ts = DNS_RRL_FOREVER;
Adam Tkac 05cf27
+		else
Adam Tkac 05cf27
+			ts = 0;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Make a new timestamp base if the current base is too old.
Adam Tkac 05cf27
+	 * All entries older than DNS_RRL_MAX_WINDOW seconds are ancient,
Adam Tkac 05cf27
+	 * useless history.  Their timestamps can be treated as if they are
Adam Tkac 05cf27
+	 * all the same.
Adam Tkac 05cf27
+	 * We only do arithmetic on more recent timestamps, so bases for
Adam Tkac 05cf27
+	 * older timestamps can be recycled provided the old timestamps are
Adam Tkac 05cf27
+	 * marked as ancient history.
Adam Tkac 05cf27
+	 * This loop is almost always very short because most entries are
Adam Tkac 05cf27
+	 * recycled after one second and any entries that need to be marked
Adam Tkac 05cf27
+	 * are older than (DNS_RRL_TS_BASES)*DNS_RRL_MAX_TS seconds.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (ts >= DNS_RRL_MAX_TS) {
Adam Tkac c9b941
+		ts_gen = (ts_gen + 1) % DNS_RRL_TS_BASES;
Adam Tkac 05cf27
+		for (e_old = ISC_LIST_TAIL(rrl->lru), i = 0;
Tomas Hozza df0cae
+		     e_old != NULL && (e_old->ts_gen == ts_gen ||
Tomas Hozza df0cae
+				       !ISC_LINK_LINKED(e_old, hlink));
Adam Tkac c9b941
+		     e_old = ISC_LIST_PREV(e_old, lru), ++i)
Adam Tkac c9b941
+		{
Tomas Hozza df0cae
+			e_old->ts_valid = ISC_FALSE;
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		if (i != 0)
Adam Tkac 05cf27
+			isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+				      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1,
Adam Tkac 05cf27
+				      "rrl new time base scanned %d entries"
Adam Tkac 05cf27
+				      " at %d for %d %d %d %d",
Adam Tkac 05cf27
+				      i, now, rrl->ts_bases[ts_gen],
Adam Tkac c9b941
+				      rrl->ts_bases[(ts_gen + 1) %
Adam Tkac c9b941
+					DNS_RRL_TS_BASES],
Adam Tkac c9b941
+				      rrl->ts_bases[(ts_gen + 2) %
Adam Tkac c9b941
+					DNS_RRL_TS_BASES],
Adam Tkac c9b941
+				      rrl->ts_bases[(ts_gen + 3) %
Adam Tkac c9b941
+					DNS_RRL_TS_BASES]);
Adam Tkac 05cf27
+		rrl->ts_gen = ts_gen;
Adam Tkac 05cf27
+		rrl->ts_bases[ts_gen] = now;
Adam Tkac 05cf27
+		ts = 0;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	e->ts_gen = ts_gen;
Adam Tkac 05cf27
+	e->ts = ts;
Adam Tkac 05cf27
+	e->ts_valid = ISC_TRUE;
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static isc_result_t
Adam Tkac 05cf27
+expand_entries(dns_rrl_t *rrl, int new) {
Adam Tkac 05cf27
+	unsigned int bsize;
Adam Tkac 05cf27
+	dns_rrl_block_t *b;
Adam Tkac 05cf27
+	dns_rrl_entry_t *e;
Adam Tkac 05cf27
+	double rate;
Adam Tkac 05cf27
+	int i;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (rrl->num_entries+new >= rrl->max_entries && rrl->max_entries != 0) {
Adam Tkac 05cf27
+		if (rrl->num_entries >= rrl->max_entries)
Adam Tkac 05cf27
+			return (ISC_R_SUCCESS);
Adam Tkac 05cf27
+		new = rrl->max_entries - rrl->num_entries;
Adam Tkac 05cf27
+		if (new <= 0)
Adam Tkac 05cf27
+			return (ISC_R_NOMEMORY);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Log expansions so that the user can tune max-table-size
Adam Tkac 05cf27
+	 * and min-table-size.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) &&
Adam Tkac 05cf27
+	    rrl->hash != NULL) {
Adam Tkac 05cf27
+		rate = rrl->probes;
Adam Tkac 05cf27
+		if (rrl->searches != 0)
Adam Tkac 05cf27
+			rate /= rrl->searches;
Adam Tkac 05cf27
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
Adam Tkac 05cf27
+			      "increase from %d to %d RRL entries with"
Adam Tkac 05cf27
+			      " %d bins; average search length %.1f",
Adam Tkac 05cf27
+			      rrl->num_entries, rrl->num_entries+new,
Adam Tkac 05cf27
+			      rrl->hash->length, rate);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	bsize = sizeof(dns_rrl_block_t) + (new-1)*sizeof(dns_rrl_entry_t);
Adam Tkac 05cf27
+	b = isc_mem_get(rrl->mctx, bsize);
Adam Tkac 05cf27
+	if (b == NULL) {
Adam Tkac 05cf27
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_FAIL,
Adam Tkac 05cf27
+			      "isc_mem_get(%d) failed for RRL entries",
Adam Tkac 05cf27
+			      bsize);
Adam Tkac 05cf27
+		return (ISC_R_NOMEMORY);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	memset(b, 0, bsize);
Adam Tkac 05cf27
+	b->size = bsize;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	e = b->entries;
Adam Tkac 05cf27
+	for (i = 0; i < new; ++i, ++e) {
Adam Tkac 05cf27
+		ISC_LINK_INIT(e, hlink);
Adam Tkac 05cf27
+		ISC_LIST_INITANDAPPEND(rrl->lru, e, lru);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	rrl->num_entries += new;
Adam Tkac 05cf27
+	ISC_LIST_INITANDAPPEND(rrl->blocks, b, link);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	return (ISC_R_SUCCESS);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static inline dns_rrl_bin_t *
Adam Tkac 05cf27
+get_bin(dns_rrl_hash_t *hash, unsigned int hval) {
Adam Tkac 05cf27
+	return (&hash->bins[hval % hash->length]);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static void
Adam Tkac 05cf27
+free_old_hash(dns_rrl_t *rrl) {
Adam Tkac 05cf27
+	dns_rrl_hash_t *old_hash;
Adam Tkac 05cf27
+	dns_rrl_bin_t *old_bin;
Adam Tkac 05cf27
+	dns_rrl_entry_t *e, *e_next;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	old_hash = rrl->old_hash;
Adam Tkac 05cf27
+	for (old_bin = &old_hash->bins[0];
Adam Tkac 05cf27
+	     old_bin < &old_hash->bins[old_hash->length];
Adam Tkac c9b941
+	     ++old_bin)
Adam Tkac c9b941
+	{
Adam Tkac 05cf27
+		for (e = ISC_LIST_HEAD(*old_bin); e != NULL; e = e_next) {
Adam Tkac 05cf27
+			e_next = ISC_LIST_NEXT(e, hlink);
Adam Tkac 05cf27
+			ISC_LINK_INIT(e, hlink);
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	isc_mem_put(rrl->mctx, old_hash,
Adam Tkac 05cf27
+		    sizeof(*old_hash)
Adam Tkac c9b941
+		      + (old_hash->length - 1) * sizeof(old_hash->bins[0]));
Adam Tkac 05cf27
+	rrl->old_hash = NULL;
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static isc_result_t
Adam Tkac 05cf27
+expand_rrl_hash(dns_rrl_t *rrl, isc_stdtime_t now) {
Adam Tkac 05cf27
+	dns_rrl_hash_t *hash;
Adam Tkac 05cf27
+	int old_bins, new_bins, hsize;
Adam Tkac 05cf27
+	double rate;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (rrl->old_hash != NULL)
Adam Tkac 05cf27
+		free_old_hash(rrl);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Most searches fail and so go to the end of the chain.
Adam Tkac 05cf27
+	 * Use a small hash table load factor.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	old_bins = (rrl->hash == NULL) ? 0 : rrl->hash->length;
Adam Tkac 05cf27
+	new_bins = old_bins/8 + old_bins;
Adam Tkac 05cf27
+	if (new_bins < rrl->num_entries)
Adam Tkac 05cf27
+		new_bins = rrl->num_entries;
Adam Tkac 05cf27
+	new_bins = hash_divisor(new_bins);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	hsize = sizeof(dns_rrl_hash_t) + (new_bins-1)*sizeof(hash->bins[0]);
Adam Tkac 05cf27
+	hash = isc_mem_get(rrl->mctx, hsize);
Adam Tkac 05cf27
+	if (hash == NULL) {
Adam Tkac 05cf27
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_FAIL,
Adam Tkac 05cf27
+			      "isc_mem_get(%d) failed for"
Adam Tkac 05cf27
+			      " RRL hash table",
Adam Tkac 05cf27
+			      hsize);
Adam Tkac 05cf27
+		return (ISC_R_NOMEMORY);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	memset(hash, 0, hsize);
Adam Tkac 05cf27
+	hash->length = new_bins;
Adam Tkac 05cf27
+	rrl->hash_gen ^= 1;
Adam Tkac 05cf27
+	hash->gen = rrl->hash_gen;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) && old_bins != 0) {
Adam Tkac 05cf27
+		rate = rrl->probes;
Adam Tkac 05cf27
+		if (rrl->searches != 0)
Adam Tkac 05cf27
+			rate /= rrl->searches;
Adam Tkac 05cf27
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
Adam Tkac 05cf27
+			      "increase from %d to %d RRL bins for"
Adam Tkac 05cf27
+			      " %d entries; average search length %.1f",
Adam Tkac 05cf27
+			      old_bins, new_bins, rrl->num_entries, rate);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	rrl->old_hash = rrl->hash;
Adam Tkac 05cf27
+	if (rrl->old_hash != NULL)
Adam Tkac 05cf27
+		rrl->old_hash->check_time = now;
Adam Tkac 05cf27
+	rrl->hash = hash;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	return (ISC_R_SUCCESS);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static void
Adam Tkac 05cf27
+ref_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, int probes, isc_stdtime_t now) {
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Make the entry most recently used.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (ISC_LIST_HEAD(rrl->lru) != e) {
Adam Tkac 05cf27
+		if (e == rrl->last_logged)
Adam Tkac 05cf27
+			rrl->last_logged = ISC_LIST_PREV(e, lru);
Adam Tkac 05cf27
+		ISC_LIST_UNLINK(rrl->lru, e, lru);
Adam Tkac 05cf27
+		ISC_LIST_PREPEND(rrl->lru, e, lru);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Expand the hash table if it is time and necessary.
Adam Tkac 05cf27
+	 * This will leave the newly referenced entry in a chain in the
Adam Tkac 05cf27
+	 * old hash table.  It will migrate to the new hash table the next
Adam Tkac 05cf27
+	 * time it is used or be cut loose when the old hash table is destroyed.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	rrl->probes += probes;
Adam Tkac 05cf27
+	++rrl->searches;
Adam Tkac 05cf27
+	if (rrl->searches > 100 &&
Adam Tkac 05cf27
+	    delta_rrl_time(rrl->hash->check_time, now) > 1) {
Adam Tkac 05cf27
+		if (rrl->probes/rrl->searches > 2)
Adam Tkac 05cf27
+			expand_rrl_hash(rrl, now);
Adam Tkac 05cf27
+		rrl->hash->check_time = now;
Adam Tkac 05cf27
+		rrl->probes = 0;
Adam Tkac 05cf27
+		rrl->searches = 0;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static inline isc_boolean_t
Adam Tkac 05cf27
+key_cmp(const dns_rrl_key_t *a, const dns_rrl_key_t *b) {
Adam Tkac 05cf27
+	if (memcmp(a, b, sizeof(dns_rrl_key_t)) == 0)
Adam Tkac 05cf27
+		return (ISC_TRUE);
Adam Tkac 05cf27
+	return (ISC_FALSE);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static inline isc_uint32_t
Adam Tkac 05cf27
+hash_key(const dns_rrl_key_t *key) {
Adam Tkac 05cf27
+	isc_uint32_t hval;
Adam Tkac 05cf27
+	int i;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	hval = key->w[0];
Adam Tkac c9b941
+	for (i = sizeof(*key) / sizeof(key->w[0]) - 1; i >= 0; --i) {
Adam Tkac 05cf27
+		hval = key->w[i] + (hval<<1);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	return (hval);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Construct the hash table key.
Adam Tkac 05cf27
+ * Use a hash of the DNS query name to save space in the database.
Adam Tkac 05cf27
+ * Collisions result in legitimate rate limiting responses for one
Adam Tkac 05cf27
+ * query name also limiting responses for other names to the
Adam Tkac 05cf27
+ * same client.  This is rare and benign enough given the large
Adam Tkac 05cf27
+ * space costs compared to keeping the entire name in the database
Adam Tkac 05cf27
+ * entry or the time costs of dynamic allocation.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+static void
Adam Tkac 05cf27
+make_key(const dns_rrl_t *rrl, dns_rrl_key_t *key,
Adam Tkac 05cf27
+	 const isc_sockaddr_t *client_addr,
Adam Tkac 05cf27
+	 dns_rdatatype_t qtype, dns_name_t *qname, dns_rdataclass_t qclass,
Adam Tkac 05cf27
+	 dns_rrl_rtype_t rtype)
Adam Tkac 05cf27
+{
Adam Tkac 05cf27
+	dns_name_t base;
Adam Tkac 05cf27
+	dns_offsets_t base_offsets;
Adam Tkac 05cf27
+	int labels, i;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	memset(key, 0, sizeof(*key));
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	key->s.rtype = rtype;
Tomas Hozza df0cae
+	if (rtype == DNS_RRL_RTYPE_QUERY) {
Adam Tkac 05cf27
+		key->s.qtype = qtype;
Tomas Hozza df0cae
+		key->s.qclass = qclass & 0xff;
Tomas Hozza df0cae
+	} else if (rtype == DNS_RRL_RTYPE_REFERRAL ||
Tomas Hozza df0cae
+		   rtype == DNS_RRL_RTYPE_NODATA) {
Tomas Hozza df0cae
+		/*
Tomas Hozza df0cae
+		 * Because there is no qtype in the empty answer sections of
Tomas Hozza df0cae
+		 * referral and NODATA responses, count them as the same.
Tomas Hozza df0cae
+		 */
Tomas Hozza df0cae
+		key->s.qclass = qclass & 0xff;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (qname != NULL && qname->labels != 0) {
Adam Tkac 05cf27
+		/*
Adam Tkac 05cf27
+		 * Ignore the first label of wildcards.
Adam Tkac 05cf27
+		 */
Adam Tkac 05cf27
+		if ((qname->attributes & DNS_NAMEATTR_WILDCARD) != 0 &&
Adam Tkac c9b941
+		    (labels = dns_name_countlabels(qname)) > 1)
Adam Tkac c9b941
+		{
Adam Tkac 05cf27
+			dns_name_init(&base, base_offsets);
Adam Tkac 05cf27
+			dns_name_getlabelsequence(qname, 1, labels-1, &base);
Adam Tkac 05cf27
+			key->s.qname_hash = dns_name_hashbylabel(&base,
Adam Tkac 05cf27
+							ISC_FALSE);
Adam Tkac 05cf27
+		} else {
Adam Tkac 05cf27
+			key->s.qname_hash = dns_name_hashbylabel(qname,
Adam Tkac 05cf27
+							ISC_FALSE);
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	switch (client_addr->type.sa.sa_family) {
Adam Tkac 05cf27
+	case AF_INET:
Adam Tkac 05cf27
+		key->s.ip[0] = (client_addr->type.sin.sin_addr.s_addr &
Adam Tkac 05cf27
+			      rrl->ipv4_mask);
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case AF_INET6:
Adam Tkac 05cf27
+		key->s.ipv6 = ISC_TRUE;
Adam Tkac 05cf27
+		memcpy(key->s.ip, &client_addr->type.sin6.sin6_addr,
Adam Tkac 05cf27
+		       sizeof(key->s.ip));
Adam Tkac 05cf27
+		for (i = 0; i < DNS_RRL_MAX_PREFIX/32; ++i)
Adam Tkac 05cf27
+			key->s.ip[i] &= rrl->ipv6_mask[i];
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+static inline dns_rrl_rate_t *
Tomas Hozza df0cae
+get_rate(dns_rrl_t *rrl, dns_rrl_rtype_t rtype) {
Tomas Hozza df0cae
+	switch (rtype) {
Tomas Hozza df0cae
+	case DNS_RRL_RTYPE_QUERY:
Tomas Hozza df0cae
+		return (&rrl->responses_per_second);
Tomas Hozza df0cae
+	case DNS_RRL_RTYPE_REFERRAL:
Tomas Hozza df0cae
+		return (&rrl->referrals_per_second);
Tomas Hozza df0cae
+	case DNS_RRL_RTYPE_NODATA:
Tomas Hozza df0cae
+		return (&rrl->nodata_per_second);
Tomas Hozza df0cae
+	case DNS_RRL_RTYPE_NXDOMAIN:
Tomas Hozza df0cae
+		return (&rrl->nxdomains_per_second);
Tomas Hozza df0cae
+	case DNS_RRL_RTYPE_ERROR:
Tomas Hozza df0cae
+		return (&rrl->errors_per_second);
Tomas Hozza df0cae
+	case DNS_RRL_RTYPE_ALL:
Tomas Hozza df0cae
+		return (&rrl->all_per_second);
Tomas Hozza df0cae
+	default:
Tomas Hozza df0cae
+		INSIST(0);
Tomas Hozza df0cae
+	}
Tomas Hozza df0cae
+	return (NULL);
Tomas Hozza df0cae
+}
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+static int
Tomas Hozza df0cae
+response_balance(dns_rrl_t *rrl, const dns_rrl_entry_t *e, int age) {
Tomas Hozza df0cae
+	dns_rrl_rate_t *ratep;
Tomas Hozza df0cae
+	int balance, rate;
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+	if (e->key.s.rtype == DNS_RRL_RTYPE_TCP) {
Tomas Hozza df0cae
+		rate = 1;
Tomas Hozza df0cae
+	} else {
Tomas Hozza df0cae
+		ratep = get_rate(rrl, e->key.s.rtype);
Tomas Hozza df0cae
+		rate = ratep->scaled;
Adam Tkac 05cf27
+	}
Tomas Hozza df0cae
+
Tomas Hozza df0cae
+	balance = e->responses + age * rate;
Adam Tkac 05cf27
+	if (balance > rate)
Adam Tkac 05cf27
+		balance = rate;
Adam Tkac 05cf27
+	return (balance);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Search for an entry for a response and optionally create it.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+static dns_rrl_entry_t *
Adam Tkac 05cf27
+get_entry(dns_rrl_t *rrl, const isc_sockaddr_t *client_addr,
Adam Tkac 05cf27
+	  dns_rdataclass_t qclass, dns_rdatatype_t qtype, dns_name_t *qname,
Adam Tkac 05cf27
+	  dns_rrl_rtype_t rtype, isc_stdtime_t now, isc_boolean_t create,
Adam Tkac 05cf27
+	  char *log_buf, unsigned int log_buf_len)
Adam Tkac 05cf27
+{
Adam Tkac 05cf27
+	dns_rrl_key_t key;
Adam Tkac 05cf27
+	isc_uint32_t hval;
Adam Tkac 05cf27
+	dns_rrl_entry_t *e;
Adam Tkac 05cf27
+	dns_rrl_hash_t *hash;
Adam Tkac 05cf27
+	dns_rrl_bin_t *new_bin, *old_bin;
Adam Tkac 05cf27
+	int probes, age;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	make_key(rrl, &key, client_addr, qtype, qname, qclass, rtype);
Adam Tkac 05cf27
+	hval = hash_key(&key);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Look for the entry in the current hash table.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	new_bin = get_bin(rrl->hash, hval);
Adam Tkac 05cf27
+	probes = 1;
Adam Tkac 05cf27
+	e = ISC_LIST_HEAD(*new_bin);
Adam Tkac 05cf27
+	while (e != NULL) {
Adam Tkac 05cf27
+		if (key_cmp(&e->key, &key)) {
Adam Tkac 05cf27
+			ref_entry(rrl, e, probes, now);
Adam Tkac 05cf27
+			return (e);
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		++probes;
Adam Tkac 05cf27
+		e = ISC_LIST_NEXT(e, hlink);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Look in the old hash table.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (rrl->old_hash != NULL) {
Adam Tkac 05cf27
+		old_bin = get_bin(rrl->old_hash, hval);
Adam Tkac 05cf27
+		e = ISC_LIST_HEAD(*old_bin);
Adam Tkac 05cf27
+		while (e != NULL) {
Adam Tkac 05cf27
+			if (key_cmp(&e->key, &key)) {
Adam Tkac 05cf27
+				ISC_LIST_UNLINK(*old_bin, e, hlink);
Adam Tkac 05cf27
+				ISC_LIST_PREPEND(*new_bin, e, hlink);
Adam Tkac 05cf27
+				e->hash_gen = rrl->hash_gen;
Adam Tkac 05cf27
+				ref_entry(rrl, e, probes, now);
Adam Tkac 05cf27
+				return (e);
Adam Tkac 05cf27
+			}
Adam Tkac c9b941
+			e = ISC_LIST_NEXT(e, hlink);
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+		/*
Adam Tkac 05cf27
+		 * Discard prevous hash table when all of its entries are old.
Adam Tkac 05cf27
+		 */
Adam Tkac 05cf27
+		age = delta_rrl_time(rrl->old_hash->check_time, now);
Adam Tkac 05cf27
+		if (age > rrl->window)
Adam Tkac 05cf27
+			free_old_hash(rrl);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (!create)
Adam Tkac 05cf27
+		return (NULL);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * The entry does not exist, so create it by finding a free entry.
Adam Tkac 05cf27
+	 * Keep currently penalized and logged entries.
Adam Tkac 05cf27
+	 * Try to make more entries if none are idle.
Adam Tkac 05cf27
+	 * Steal the oldest entry if we cannot create more.
Adam Tkac 05cf27
+	 */
Adam Tkac c9b941
+	for (e = ISC_LIST_TAIL(rrl->lru);
Adam Tkac c9b941
+	     e != NULL;
Adam Tkac c9b941
+	     e = ISC_LIST_PREV(e, lru))
Adam Tkac c9b941
+	{
Adam Tkac 05cf27
+		if (!ISC_LINK_LINKED(e, hlink))
Adam Tkac 05cf27
+			break;
Adam Tkac 05cf27
+		age = get_age(rrl, e, now);
Adam Tkac 05cf27
+		if (age <= 1) {
Adam Tkac 05cf27
+			e = NULL;
Adam Tkac 05cf27
+			break;
Adam Tkac 05cf27
+		}
Tomas Hozza df0cae
+		if (!e->logged && response_balance(rrl, e, age) > 0)
Adam Tkac 05cf27
+			break;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	if (e == NULL) {
Adam Tkac 05cf27
+		expand_entries(rrl, ISC_MIN((rrl->num_entries+1)/2, 1000));
Adam Tkac 05cf27
+		e = ISC_LIST_TAIL(rrl->lru);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	if (e->logged)
Adam Tkac 05cf27
+		log_end(rrl, e, ISC_TRUE, log_buf, log_buf_len);
Adam Tkac 05cf27
+	if (ISC_LINK_LINKED(e, hlink)) {
Adam Tkac 05cf27
+		if (e->hash_gen == rrl->hash_gen)
Adam Tkac 05cf27
+			hash = rrl->hash;
Adam Tkac 05cf27
+		else
Adam Tkac 05cf27
+			hash = rrl->old_hash;
Adam Tkac 05cf27
+		old_bin = get_bin(hash, hash_key(&e->key));
Adam Tkac 05cf27
+		ISC_LIST_UNLINK(*old_bin, e, hlink);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	ISC_LIST_PREPEND(*new_bin, e, hlink);
Adam Tkac 05cf27
+	e->hash_gen = rrl->hash_gen;
Adam Tkac 05cf27
+	e->key = key;
Adam Tkac 05cf27
+	e->ts_valid = ISC_FALSE;
Adam Tkac 05cf27
+	ref_entry(rrl, e, probes, now);
Adam Tkac 05cf27
+	return (e);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static void
Adam Tkac 05cf27
+debit_log(const dns_rrl_entry_t *e, int age, const char *action) {
Adam Tkac 05cf27
+	char buf[sizeof("age=12345678")];
Adam Tkac 05cf27
+	const char *age_str;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (age == DNS_RRL_FOREVER) {
Adam Tkac 05cf27
+		age_str = "";
Adam Tkac 05cf27
+	} else {
Adam Tkac 05cf27
+		snprintf(buf, sizeof(buf), "age=%d", age);
Adam Tkac 05cf27
+		age_str = buf;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+		      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG3,
Adam Tkac 05cf27
+		      "rrl %08x %6s  responses=%-3d %s",
Adam Tkac 05cf27
+		      hash_key(&e->key), age_str, e->responses, action);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static inline dns_rrl_result_t
Adam Tkac 05cf27
+debit_rrl_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, double qps, double scale,
Adam Tkac 05cf27
+		const isc_sockaddr_t *client_addr, isc_stdtime_t now,
Adam Tkac 05cf27
+		char *log_buf, unsigned int log_buf_len)
Adam Tkac 05cf27
+{
Tomas Hozza df0cae
+	int rate, new_rate, slip, new_slip, age, log_secs, min;
Tomas Hozza df0cae
+	dns_rrl_rate_t *ratep;
Adam Tkac 05cf27
+	dns_rrl_entry_t const *credit_e;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Pick the rate counter.
Adam Tkac 05cf27
+	 * Optionally adjust the rate by the estimated query/second rate.
Adam Tkac 05cf27
+	 */
Tomas Hozza df0cae
+	ratep = get_rate(rrl, e->key.s.rtype);
Tomas Hozza df0cae
+	rate = ratep->r;
Adam Tkac 05cf27
+	if (rate == 0)
Adam Tkac 05cf27
+		return (DNS_RRL_RESULT_OK);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (scale < 1.0) {
Adam Tkac 05cf27
+		/*
Adam Tkac 05cf27
+		 * The limit for clients that have used TCP is not scaled.
Adam Tkac 05cf27
+		 */
Adam Tkac 05cf27
+		credit_e = get_entry(rrl, client_addr,
Adam Tkac 05cf27
+				     0, dns_rdatatype_none, NULL,
Adam Tkac 05cf27
+				     DNS_RRL_RTYPE_TCP, now, ISC_FALSE,
Adam Tkac 05cf27
+				     log_buf, log_buf_len);
Adam Tkac 05cf27
+		if (credit_e != NULL) {
Adam Tkac 05cf27
+			age = get_age(rrl, e, now);
Adam Tkac 05cf27
+			if (age < rrl->window)
Adam Tkac 05cf27
+				scale = 1.0;
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	if (scale < 1.0) {
Adam Tkac c9b941
+		new_rate = (int) (rate * scale);
Adam Tkac 05cf27
+		if (new_rate < 1)
Adam Tkac 05cf27
+			new_rate = 1;
Tomas Hozza df0cae
+		if (ratep->scaled != new_rate) {
Tomas Hozza df0cae
+			isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Tomas Hozza df0cae
+				      DNS_LOGMODULE_REQUEST,
Tomas Hozza df0cae
+				      DNS_RRL_LOG_DEBUG1,
Tomas Hozza df0cae
+				      "%d qps scaled %s by %.2f"
Tomas Hozza df0cae
+				      " from %d to %d",
Tomas Hozza df0cae
+				      (int)qps, ratep->str, scale,
Tomas Hozza df0cae
+				      rate, new_rate);
Adam Tkac 05cf27
+			rate = new_rate;
Tomas Hozza df0cae
+			ratep->scaled = rate;
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	min = -rrl->window * rate;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Treat time jumps into the recent past as no time.
Adam Tkac 05cf27
+	 * Treat entries older than the window as if they were just created
Adam Tkac 05cf27
+	 * Credit other entries.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	age = get_age(rrl, e, now);
Adam Tkac 05cf27
+	if (age > 0) {
Adam Tkac 05cf27
+		/*
Adam Tkac 05cf27
+		 * Credit tokens earned during elapsed time.
Adam Tkac 05cf27
+		 */
Adam Tkac 05cf27
+		if (age > rrl->window) {
Adam Tkac 05cf27
+			e->responses = rate;
Adam Tkac 05cf27
+			e->slip_cnt = 0;
Adam Tkac 05cf27
+		} else {
Adam Tkac 05cf27
+			e->responses += rate*age;
Adam Tkac 05cf27
+			if (e->responses > rate) {
Adam Tkac 05cf27
+				e->responses = rate;
Adam Tkac 05cf27
+				e->slip_cnt = 0;
Adam Tkac 05cf27
+			}
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		/*
Adam Tkac 05cf27
+		 * Find the seconds since last log message without overflowing
Adam Tkac 05cf27
+		 * small counter.  This counter is reset when an entry is
Adam Tkac 05cf27
+		 * created.  It is not necessarily reset when some requests
Adam Tkac 05cf27
+		 * are answered provided other requests continue to be dropped
Adam Tkac 05cf27
+		 * or slipped.  This can happen when the request rate is just
Adam Tkac 05cf27
+		 * at the limit.
Adam Tkac 05cf27
+		 */
Adam Tkac 05cf27
+		if (e->logged) {
Adam Tkac 05cf27
+			log_secs = e->log_secs;
Adam Tkac 05cf27
+			log_secs += age;
Adam Tkac 05cf27
+			if (log_secs > DNS_RRL_MAX_LOG_SECS || log_secs < 0)
Adam Tkac 05cf27
+				log_secs = DNS_RRL_MAX_LOG_SECS;
Adam Tkac 05cf27
+			e->log_secs = log_secs;
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	set_age(rrl, e, now);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Debit the entry for this response.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (--e->responses >= 0) {
Adam Tkac 05cf27
+		if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3))
Adam Tkac 05cf27
+			debit_log(e, age, "");
Adam Tkac 05cf27
+		return (DNS_RRL_RESULT_OK);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (e->responses < min)
Adam Tkac 05cf27
+		e->responses = min;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Drop this response unless it should slip or leak.
Adam Tkac 05cf27
+	 */
Tomas Hozza df0cae
+	slip = rrl->slip.r;
Adam Tkac 05cf27
+	if (slip > 2 && scale < 1.0) {
Adam Tkac c9b941
+		new_slip = (int) (slip * scale);
Adam Tkac 05cf27
+		if (new_slip < 2)
Adam Tkac 05cf27
+			new_slip = 2;
Tomas Hozza df0cae
+		if (rrl->slip.scaled != new_slip) {
Tomas Hozza df0cae
+			isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Tomas Hozza df0cae
+				      DNS_LOGMODULE_REQUEST,
Tomas Hozza df0cae
+				      DNS_RRL_LOG_DEBUG1,
Tomas Hozza df0cae
+				      "%d qps scaled slip"
Tomas Hozza df0cae
+				      " by %.2f from %d to %d",
Tomas Hozza df0cae
+				      (int)qps, scale,
Tomas Hozza df0cae
+				      slip, new_slip);
Adam Tkac 05cf27
+			slip = new_slip;
Tomas Hozza df0cae
+			rrl->slip.scaled = slip;
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	if (slip != 0 && e->key.s.rtype != DNS_RRL_RTYPE_ALL) {
Adam Tkac 05cf27
+		if (e->slip_cnt++ == 0) {
Adam Tkac c9b941
+			if ((int) e->slip_cnt >= slip)
Adam Tkac c9b941
+				e->slip_cnt = 0;
Adam Tkac 05cf27
+			if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3))
Adam Tkac 05cf27
+				debit_log(e, age, "slip");
Adam Tkac 05cf27
+			return (DNS_RRL_RESULT_SLIP);
Adam Tkac c9b941
+		} else if ((int) e->slip_cnt >= slip) {
Adam Tkac 05cf27
+			e->slip_cnt = 0;
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3))
Adam Tkac 05cf27
+		debit_log(e, age, "drop");
Adam Tkac 05cf27
+	return (DNS_RRL_RESULT_DROP);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static inline dns_rrl_qname_buf_t *
Adam Tkac 05cf27
+get_qname(dns_rrl_t *rrl, const dns_rrl_entry_t *e) {
Adam Tkac 05cf27
+	dns_rrl_qname_buf_t *qbuf;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	qbuf = rrl->qnames[e->log_qname];
Adam Tkac c9b941
+	if (qbuf == NULL || qbuf->e != e)
Adam Tkac 05cf27
+		return (NULL);
Adam Tkac 05cf27
+	return (qbuf);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static inline void
Adam Tkac 05cf27
+free_qname(dns_rrl_t *rrl, dns_rrl_entry_t *e) {
Adam Tkac 05cf27
+	dns_rrl_qname_buf_t *qbuf;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	qbuf = get_qname(rrl, e);
Adam Tkac 05cf27
+	if (qbuf != NULL) {
Adam Tkac 05cf27
+		qbuf->e = NULL;
Adam Tkac 05cf27
+		ISC_LIST_APPEND(rrl->qname_free, qbuf, link);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static void
Adam Tkac c9b941
+add_log_str(isc_buffer_t *lb, const char *str, unsigned int str_len) {
Adam Tkac 05cf27
+	isc_region_t region;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	isc_buffer_availableregion(lb, &region);
Adam Tkac 05cf27
+	if (str_len >= region.length) {
Adam Tkac 05cf27
+		if (region.length <= 0)
Adam Tkac 05cf27
+			return;
Adam Tkac 05cf27
+		str_len = region.length;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	memcpy(region.base, str, str_len);
Adam Tkac 05cf27
+	isc_buffer_add(lb, str_len);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#define ADD_LOG_CSTR(eb, s) add_log_str(eb, s, sizeof(s)-1)
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Build strings for the logs
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+static void
Adam Tkac 05cf27
+make_log_buf(dns_rrl_t *rrl, dns_rrl_entry_t *e,
Adam Tkac 05cf27
+	     const char *str1, const char *str2, isc_boolean_t plural,
Adam Tkac 05cf27
+	     dns_name_t *qname, isc_boolean_t save_qname,
Adam Tkac 05cf27
+	     dns_rrl_result_t rrl_result, isc_result_t resp_result,
Adam Tkac 05cf27
+	     char *log_buf, unsigned int log_buf_len)
Adam Tkac 05cf27
+{
Adam Tkac 05cf27
+	isc_buffer_t lb;
Adam Tkac 05cf27
+	dns_rrl_qname_buf_t *qbuf;
Adam Tkac 05cf27
+	isc_netaddr_t cidr;
Adam Tkac 05cf27
+	char strbuf[ISC_MAX(sizeof("/123"), sizeof("  (12345678)"))];
Adam Tkac 05cf27
+	const char *rstr;
Adam Tkac 05cf27
+	isc_result_t msg_result;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (log_buf_len <= 1) {
Adam Tkac 05cf27
+		if (log_buf_len == 1)
Adam Tkac 05cf27
+			log_buf[0] = '\0';
Adam Tkac 05cf27
+		return;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	isc_buffer_init(&lb, log_buf, log_buf_len-1);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (str1 != NULL)
Adam Tkac 05cf27
+		add_log_str(&lb, str1, strlen(str1));
Adam Tkac 05cf27
+	if (str2 != NULL)
Adam Tkac 05cf27
+		add_log_str(&lb, str2, strlen(str2));
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	switch (rrl_result) {
Adam Tkac 05cf27
+	case DNS_RRL_RESULT_OK:
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case DNS_RRL_RESULT_DROP:
Adam Tkac 05cf27
+		ADD_LOG_CSTR(&lb, "drop ");
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case DNS_RRL_RESULT_SLIP:
Adam Tkac 05cf27
+		ADD_LOG_CSTR(&lb, "slip ");
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	default:
Adam Tkac 05cf27
+		INSIST(0);
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	switch (e->key.s.rtype) {
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_QUERY:
Adam Tkac 05cf27
+		break;
Tomas Hozza df0cae
+	case DNS_RRL_RTYPE_REFERRAL:
Tomas Hozza df0cae
+		ADD_LOG_CSTR(&lb, "referral ");
Tomas Hozza df0cae
+		break;
Tomas Hozza df0cae
+	case DNS_RRL_RTYPE_NODATA:
Tomas Hozza df0cae
+		ADD_LOG_CSTR(&lb, "NODATA ");
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_NXDOMAIN:
Tomas Hozza df0cae
+		ADD_LOG_CSTR(&lb, "NXDOMAIN ");
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_ERROR:
Adam Tkac 05cf27
+		if (resp_result == ISC_R_SUCCESS) {
Tomas Hozza df0cae
+			ADD_LOG_CSTR(&lb, "error ");
Adam Tkac 05cf27
+		} else {
Adam Tkac 05cf27
+			rstr = isc_result_totext(resp_result);
Adam Tkac c9b941
+			add_log_str(&lb, rstr, strlen(rstr));
Tomas Hozza df0cae
+			ADD_LOG_CSTR(&lb, " error ");
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_ALL:
Tomas Hozza df0cae
+		ADD_LOG_CSTR(&lb, "all ");
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	default:
Adam Tkac 05cf27
+		INSIST(0);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (plural)
Tomas Hozza df0cae
+		ADD_LOG_CSTR(&lb, "responses to ");
Adam Tkac 05cf27
+	else
Tomas Hozza df0cae
+		ADD_LOG_CSTR(&lb, "response to ");
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	memset(&cidr, 0, sizeof(cidr));
Adam Tkac 05cf27
+	if (e->key.s.ipv6) {
Adam Tkac 05cf27
+		snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv6_prefixlen);
Adam Tkac 05cf27
+		cidr.family = AF_INET6;
Adam Tkac 05cf27
+		memset(&cidr.type.in6, 0,  sizeof(cidr.type.in6));
Adam Tkac 05cf27
+		memcpy(&cidr.type.in6, e->key.s.ip, sizeof(e->key.s.ip));
Adam Tkac 05cf27
+	} else {
Adam Tkac 05cf27
+		snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv4_prefixlen);
Adam Tkac 05cf27
+		cidr.family = AF_INET;
Adam Tkac 05cf27
+		cidr.type.in.s_addr = e->key.s.ip[0];
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	msg_result = isc_netaddr_totext(&cidr, &lb);
Adam Tkac 05cf27
+	if (msg_result != ISC_R_SUCCESS)
Adam Tkac 05cf27
+		ADD_LOG_CSTR(&lb, "?");
Adam Tkac 05cf27
+	add_log_str(&lb, strbuf, strlen(strbuf));
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY ||
Tomas Hozza df0cae
+	    e->key.s.rtype == DNS_RRL_RTYPE_REFERRAL ||
Tomas Hozza df0cae
+	    e->key.s.rtype == DNS_RRL_RTYPE_NODATA ||
Adam Tkac 05cf27
+	    e->key.s.rtype == DNS_RRL_RTYPE_NXDOMAIN) {
Adam Tkac 05cf27
+		qbuf = get_qname(rrl, e);
Adam Tkac 05cf27
+		if (save_qname && qbuf == NULL &&
Adam Tkac 05cf27
+		    qname != NULL && dns_name_isabsolute(qname)) {
Adam Tkac 05cf27
+			/*
Adam Tkac 05cf27
+			 * Capture the qname for the "stop limiting" message.
Adam Tkac 05cf27
+			 */
Adam Tkac 05cf27
+			qbuf = ISC_LIST_TAIL(rrl->qname_free);
Adam Tkac 05cf27
+			if (qbuf != NULL) {
Adam Tkac 05cf27
+				ISC_LIST_UNLINK(rrl->qname_free, qbuf, link);
Adam Tkac 05cf27
+			} else if (rrl->num_qnames < DNS_RRL_QNAMES) {
Adam Tkac 05cf27
+				qbuf = isc_mem_get(rrl->mctx, sizeof(*qbuf));
Adam Tkac 05cf27
+				if (qbuf != NULL) {
Adam Tkac 05cf27
+					memset(qbuf, 0, sizeof(*qbuf));
Adam Tkac c9b941
+					ISC_LINK_INIT(qbuf, link);
Adam Tkac 05cf27
+					qbuf->index = rrl->num_qnames;
Adam Tkac 05cf27
+					rrl->qnames[rrl->num_qnames++] = qbuf;
Adam Tkac 05cf27
+				} else {
Adam Tkac 05cf27
+					isc_log_write(dns_lctx,
Adam Tkac 05cf27
+						      DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+						      DNS_LOGMODULE_REQUEST,
Adam Tkac 05cf27
+						      DNS_RRL_LOG_FAIL,
Adam Tkac 05cf27
+						      "isc_mem_get(%d)"
Adam Tkac 05cf27
+						      " failed for RRL qname",
Adam Tkac 05cf27
+						      (int)sizeof(*qbuf));
Adam Tkac 05cf27
+				}
Adam Tkac 05cf27
+			}
Adam Tkac 05cf27
+			if (qbuf != NULL) {
Adam Tkac 05cf27
+				e->log_qname = qbuf->index;
Adam Tkac 05cf27
+				qbuf->e = e;
Adam Tkac 05cf27
+				dns_fixedname_init(&qbuf->qname);
Adam Tkac 05cf27
+				dns_name_copy(qname,
Adam Tkac 05cf27
+					      dns_fixedname_name(&qbuf->qname),
Adam Tkac 05cf27
+					      NULL);
Adam Tkac 05cf27
+			}
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		if (qbuf != NULL)
Adam Tkac 05cf27
+			qname = dns_fixedname_name(&qbuf->qname);
Adam Tkac 05cf27
+		if (qname != NULL) {
Adam Tkac 05cf27
+			ADD_LOG_CSTR(&lb, " for ");
Adam Tkac c9b941
+			(void)dns_name_totext(qname, ISC_TRUE, &lb);
Adam Tkac 05cf27
+		} else {
Adam Tkac 05cf27
+			ADD_LOG_CSTR(&lb, " for (?)");
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		if (e->key.s.rtype != DNS_RRL_RTYPE_NXDOMAIN) {
Adam Tkac 05cf27
+			ADD_LOG_CSTR(&lb, " ");
Adam Tkac c9b941
+			(void)dns_rdataclass_totext(e->key.s.qclass, &lb);
Tomas Hozza df0cae
+			if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY) {
Tomas Hozza df0cae
+				ADD_LOG_CSTR(&lb, " ");
Tomas Hozza df0cae
+				(void)dns_rdatatype_totext(e->key.s.qtype, &lb);
Tomas Hozza df0cae
+			}
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		snprintf(strbuf, sizeof(strbuf), "  (%08x)",
Adam Tkac 05cf27
+			 e->key.s.qname_hash);
Adam Tkac 05cf27
+		add_log_str(&lb, strbuf, strlen(strbuf));
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * We saved room for '\0'.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	log_buf[isc_buffer_usedlength(&lb)] = '\0';
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static void
Adam Tkac 05cf27
+log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_boolean_t early,
Adam Tkac 05cf27
+	char *log_buf, unsigned int log_buf_len)
Adam Tkac 05cf27
+{
Adam Tkac 05cf27
+	if (e->logged) {
Adam Tkac 05cf27
+		make_log_buf(rrl, e,
Adam Tkac 05cf27
+			     early ? "*" : NULL,
Adam Tkac 05cf27
+			     rrl->log_only ? "would stop limiting "
Adam Tkac 05cf27
+					   : "stop limiting ",
Adam Tkac 05cf27
+			     ISC_TRUE, NULL, ISC_FALSE,
Adam Tkac 05cf27
+			     DNS_RRL_RESULT_OK, ISC_R_SUCCESS,
Adam Tkac 05cf27
+			     log_buf, log_buf_len);
Adam Tkac 05cf27
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
Adam Tkac 05cf27
+			      "%s", log_buf);
Adam Tkac 05cf27
+		free_qname(rrl, e);
Adam Tkac 05cf27
+		e->logged = ISC_FALSE;
Adam Tkac 05cf27
+		--rrl->num_logged;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Log messages for streams that have stopped being rate limited.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+static void
Adam Tkac 05cf27
+log_stops(dns_rrl_t *rrl, isc_stdtime_t now, int limit,
Adam Tkac 05cf27
+	  char *log_buf, unsigned int log_buf_len)
Adam Tkac 05cf27
+{
Adam Tkac 05cf27
+	dns_rrl_entry_t *e;
Adam Tkac 05cf27
+	int age;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	for (e = rrl->last_logged; e != NULL; e = ISC_LIST_PREV(e, lru)) {
Adam Tkac 05cf27
+		if (!e->logged)
Adam Tkac 05cf27
+			continue;
Adam Tkac 05cf27
+		if (now != 0) {
Adam Tkac 05cf27
+			age = get_age(rrl, e, now);
Adam Tkac 05cf27
+			if (age < DNS_RRL_STOP_LOG_SECS ||
Adam Tkac 05cf27
+			    response_balance(rrl, e, age) < 0)
Adam Tkac 05cf27
+				break;
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+		log_end(rrl, e, now == 0, log_buf, log_buf_len);
Adam Tkac 05cf27
+		if (rrl->num_logged <= 0)
Adam Tkac 05cf27
+			break;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+		/*
Adam Tkac 05cf27
+		 * Too many messages could stall real work.
Adam Tkac 05cf27
+		 */
Adam Tkac 05cf27
+		if (--limit < 0) {
Adam Tkac 05cf27
+			rrl->last_logged = ISC_LIST_PREV(e, lru);
Adam Tkac 05cf27
+			return;
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	if (e == NULL) {
Adam Tkac 05cf27
+		INSIST(rrl->num_logged == 0);
Adam Tkac 05cf27
+		rrl->log_stops_time = now;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	rrl->last_logged = e;
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * Main rate limit interface.
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+dns_rrl_result_t
Adam Tkac 05cf27
+dns_rrl(dns_view_t *view,
Adam Tkac 05cf27
+	const isc_sockaddr_t *client_addr, isc_boolean_t is_tcp,
Adam Tkac 05cf27
+	dns_rdataclass_t qclass, dns_rdatatype_t qtype,
Adam Tkac 05cf27
+	dns_name_t *qname, isc_result_t resp_result, isc_stdtime_t now,
Adam Tkac 05cf27
+	isc_boolean_t wouldlog, char *log_buf, unsigned int log_buf_len)
Adam Tkac 05cf27
+{
Adam Tkac 05cf27
+	dns_rrl_t *rrl;
Adam Tkac 05cf27
+	dns_rrl_rtype_t rtype;
Adam Tkac 05cf27
+	dns_rrl_entry_t *e;
Adam Tkac 05cf27
+	isc_netaddr_t netclient;
Adam Tkac 05cf27
+	int secs;
Adam Tkac 05cf27
+	double qps, scale;
Adam Tkac 05cf27
+	int exempt_match;
Adam Tkac 05cf27
+	isc_result_t result;
Adam Tkac 05cf27
+	dns_rrl_result_t rrl_result;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	INSIST(log_buf != NULL && log_buf_len > 0);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	rrl = view->rrl;
Adam Tkac 05cf27
+	if (rrl->exempt != NULL) {
Adam Tkac 05cf27
+		isc_netaddr_fromsockaddr(&netclient, client_addr);
Adam Tkac 05cf27
+		result = dns_acl_match(&netclient, NULL, rrl->exempt,
Adam Tkac 05cf27
+				       &view->aclenv, &exempt_match, NULL);
Adam Tkac 05cf27
+		if (result == ISC_R_SUCCESS && exempt_match > 0)
Adam Tkac 05cf27
+			return (DNS_RRL_RESULT_OK);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	LOCK(&rrl->lock);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Estimate total query per second rate when scaling by qps.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (rrl->qps_scale == 0) {
Adam Tkac 05cf27
+		qps = 0.0;
Adam Tkac 05cf27
+		scale = 1.0;
Adam Tkac 05cf27
+	} else {
Adam Tkac 05cf27
+		++rrl->qps_responses;
Adam Tkac 05cf27
+		secs = delta_rrl_time(rrl->qps_time, now);
Adam Tkac 05cf27
+		if (secs <= 0) {
Adam Tkac 05cf27
+			qps = rrl->qps;
Adam Tkac 05cf27
+		} else {
Adam Tkac 05cf27
+			qps = (1.0*rrl->qps_responses) / secs;
Adam Tkac 05cf27
+			if (secs >= rrl->window) {
Adam Tkac 05cf27
+				if (isc_log_wouldlog(dns_lctx,
Adam Tkac 05cf27
+						     DNS_RRL_LOG_DEBUG3))
Adam Tkac 05cf27
+					isc_log_write(dns_lctx,
Adam Tkac 05cf27
+						      DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+						      DNS_LOGMODULE_REQUEST,
Adam Tkac 05cf27
+						      DNS_RRL_LOG_DEBUG3,
Adam Tkac 05cf27
+						      "%d responses/%d seconds"
Adam Tkac 05cf27
+						      " = %d qps",
Adam Tkac 05cf27
+						      rrl->qps_responses, secs,
Adam Tkac 05cf27
+						      (int)qps);
Adam Tkac 05cf27
+				rrl->qps = qps;
Adam Tkac 05cf27
+				rrl->qps_responses = 0;
Adam Tkac 05cf27
+				rrl->qps_time = now;
Adam Tkac 05cf27
+			} else if (qps < rrl->qps) {
Adam Tkac 05cf27
+				qps = rrl->qps;
Adam Tkac 05cf27
+			}
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		scale = rrl->qps_scale / qps;
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Do maintenance once per second.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (rrl->num_logged > 0 && rrl->log_stops_time != now)
Adam Tkac 05cf27
+		log_stops(rrl, now, 8, log_buf, log_buf_len);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Notice TCP responses when scaling limits by qps.
Adam Tkac 05cf27
+	 * Do not try to rate limit TCP responses.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (is_tcp) {
Adam Tkac 05cf27
+		if (scale < 1.0) {
Adam Tkac 05cf27
+			e = get_entry(rrl, client_addr,
Adam Tkac 05cf27
+				      0, dns_rdatatype_none, NULL,
Adam Tkac 05cf27
+				      DNS_RRL_RTYPE_TCP, now, ISC_TRUE,
Adam Tkac 05cf27
+				      log_buf, log_buf_len);
Adam Tkac 05cf27
+			if (e != NULL) {
Adam Tkac 05cf27
+				e->responses = -(rrl->window+1);
Adam Tkac 05cf27
+				set_age(rrl, e, now);
Adam Tkac 05cf27
+			}
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		UNLOCK(&rrl->lock);
Adam Tkac 05cf27
+		return (ISC_R_SUCCESS);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Find the right kind of entry, creating it if necessary.
Adam Tkac 05cf27
+	 * If that is impossible, then nothing more can be done
Adam Tkac 05cf27
+	 */
Tomas Hozza df0cae
+	switch (resp_result) {
Tomas Hozza df0cae
+	case ISC_R_SUCCESS:
Adam Tkac 05cf27
+		rtype = DNS_RRL_RTYPE_QUERY;
Tomas Hozza df0cae
+		break;
Tomas Hozza df0cae
+	case DNS_R_DELEGATION:
Tomas Hozza df0cae
+		rtype = DNS_RRL_RTYPE_REFERRAL;
Tomas Hozza df0cae
+		break;
Tomas Hozza df0cae
+	case DNS_R_NXRRSET:
Tomas Hozza df0cae
+		rtype = DNS_RRL_RTYPE_NODATA;
Tomas Hozza df0cae
+		break;
Tomas Hozza df0cae
+	case DNS_R_NXDOMAIN:
Adam Tkac 05cf27
+		rtype = DNS_RRL_RTYPE_NXDOMAIN;
Tomas Hozza df0cae
+		break;
Tomas Hozza df0cae
+	default:
Adam Tkac 05cf27
+		rtype = DNS_RRL_RTYPE_ERROR;
Tomas Hozza df0cae
+		break;
Tomas Hozza df0cae
+	}
Adam Tkac 05cf27
+	e = get_entry(rrl, client_addr, qclass, qtype, qname, rtype,
Adam Tkac 05cf27
+		      now, ISC_TRUE, log_buf, log_buf_len);
Adam Tkac 05cf27
+	if (e == NULL) {
Adam Tkac 05cf27
+		UNLOCK(&rrl->lock);
Adam Tkac 05cf27
+		return (DNS_RRL_RESULT_OK);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) {
Adam Tkac 05cf27
+		/*
Adam Tkac 05cf27
+		 * Do not worry about speed or releasing the lock.
Adam Tkac 05cf27
+		 * This message appears before messages from debit_rrl_entry().
Adam Tkac 05cf27
+		 */
Adam Tkac 05cf27
+		make_log_buf(rrl, e, "consider limiting ", NULL, ISC_FALSE,
Adam Tkac 05cf27
+			     qname, ISC_FALSE, DNS_RRL_RESULT_OK, resp_result,
Adam Tkac 05cf27
+			     log_buf, log_buf_len);
Adam Tkac 05cf27
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1,
Adam Tkac 05cf27
+			      "%s", log_buf);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	rrl_result = debit_rrl_entry(rrl, e, qps, scale, client_addr, now,
Adam Tkac 05cf27
+				     log_buf, log_buf_len);
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+	if (rrl->all_per_second.r != 0) {
Adam Tkac 05cf27
+		/*
Adam Tkac 05cf27
+		 * We must debit the all-per-second token bucket if we have
Adam Tkac 05cf27
+		 * an all-per-second limit for the IP address.
Adam Tkac 05cf27
+		 * The all-per-second limit determines the log message
Adam Tkac 05cf27
+		 * when both limits are hit.
Adam Tkac 05cf27
+		 * The response limiting must continue if the
Adam Tkac 05cf27
+		 * all-per-second limiting lapses.
Adam Tkac 05cf27
+		 */
Adam Tkac 05cf27
+		dns_rrl_entry_t *e_all;
Adam Tkac 05cf27
+		dns_rrl_result_t rrl_all_result;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+		e_all = get_entry(rrl, client_addr,
Adam Tkac 05cf27
+				  0, dns_rdatatype_none, NULL,
Adam Tkac 05cf27
+				  DNS_RRL_RTYPE_ALL, now, ISC_TRUE,
Adam Tkac 05cf27
+				  log_buf, log_buf_len);
Adam Tkac 05cf27
+		if (e_all == NULL) {
Adam Tkac 05cf27
+			UNLOCK(&rrl->lock);
Adam Tkac 05cf27
+			return (DNS_RRL_RESULT_OK);
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		rrl_all_result = debit_rrl_entry(rrl, e_all, qps, scale,
Adam Tkac 05cf27
+						 client_addr, now,
Adam Tkac 05cf27
+						 log_buf, log_buf_len);
Adam Tkac 05cf27
+		if (rrl_all_result != DNS_RRL_RESULT_OK) {
Adam Tkac 05cf27
+			int level;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+			e = e_all;
Adam Tkac 05cf27
+			rrl_result = rrl_all_result;
Adam Tkac 05cf27
+			if (rrl_result == DNS_RRL_RESULT_OK)
Adam Tkac 05cf27
+				level = DNS_RRL_LOG_DEBUG2;
Adam Tkac 05cf27
+			else
Adam Tkac 05cf27
+				level = DNS_RRL_LOG_DEBUG1;
Adam Tkac 05cf27
+			if (isc_log_wouldlog(dns_lctx, level)) {
Adam Tkac 05cf27
+				make_log_buf(rrl, e,
Adam Tkac 05cf27
+					     "prefer all-per-second limiting ",
Adam Tkac 05cf27
+					     NULL, ISC_TRUE, qname, ISC_FALSE,
Adam Tkac 05cf27
+					     DNS_RRL_RESULT_OK, resp_result,
Adam Tkac 05cf27
+					     log_buf, log_buf_len);
Adam Tkac 05cf27
+				isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+					      DNS_LOGMODULE_REQUEST, level,
Adam Tkac 05cf27
+					      "%s", log_buf);
Adam Tkac 05cf27
+			}
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (rrl_result == DNS_RRL_RESULT_OK) {
Adam Tkac 05cf27
+		UNLOCK(&rrl->lock);
Adam Tkac 05cf27
+		return (DNS_RRL_RESULT_OK);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Log occassionally in the rate-limit category.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if ((!e->logged || e->log_secs >= DNS_RRL_MAX_LOG_SECS) &&
Adam Tkac 05cf27
+	    isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP)) {
Adam Tkac 05cf27
+		make_log_buf(rrl, e, rrl->log_only ? "would " : NULL,
Adam Tkac 05cf27
+			     e->logged ? "continue limiting " : "limit ",
Adam Tkac 05cf27
+			     ISC_TRUE, qname, ISC_TRUE,
Adam Tkac 05cf27
+			     DNS_RRL_RESULT_OK, resp_result,
Adam Tkac 05cf27
+			     log_buf, log_buf_len);
Adam Tkac 05cf27
+		if (!e->logged) {
Adam Tkac 05cf27
+			e->logged = ISC_TRUE;
Adam Tkac 05cf27
+			if (++rrl->num_logged <= 1)
Adam Tkac 05cf27
+				rrl->last_logged = e;
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		e->log_secs = 0;
Adam Tkac c9b941
+
Adam Tkac 05cf27
+		/*
Adam Tkac 05cf27
+		 * Avoid holding the lock.
Adam Tkac 05cf27
+		 */
Adam Tkac 05cf27
+		if (!wouldlog) {
Adam Tkac 05cf27
+			UNLOCK(&rrl->lock);
Adam Tkac 05cf27
+			e = NULL;
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+			      DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
Adam Tkac 05cf27
+			      "%s", log_buf);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Make a log message for the caller.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (wouldlog)
Tomas Hozza df0cae
+		make_log_buf(rrl, e,
Tomas Hozza df0cae
+			     rrl->log_only ? "would rate limit " : "rate limit ",
Adam Tkac 05cf27
+			     NULL, ISC_FALSE, qname, ISC_FALSE,
Adam Tkac 05cf27
+			     rrl_result, resp_result, log_buf, log_buf_len);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (e != NULL) {
Adam Tkac 05cf27
+		/*
Adam Tkac c9b941
+		 * Do not save the qname unless we might need it for
Adam Tkac 05cf27
+		 * the ending log message.
Adam Tkac 05cf27
+		 */
Adam Tkac 05cf27
+		if (!e->logged)
Adam Tkac 05cf27
+			free_qname(rrl, e);
Adam Tkac 05cf27
+		UNLOCK(&rrl->lock);
Adam Tkac 05cf27
+	}
Adam Tkac c9b941
+
Adam Tkac 05cf27
+	return (rrl_result);
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+void
Adam Tkac 05cf27
+dns_rrl_view_destroy(dns_view_t *view) {
Adam Tkac 05cf27
+	dns_rrl_t *rrl;
Adam Tkac 05cf27
+	dns_rrl_block_t *b;
Adam Tkac 05cf27
+	dns_rrl_hash_t *h;
Adam Tkac 05cf27
+	char log_buf[DNS_RRL_LOG_BUF_LEN];
Adam Tkac 05cf27
+	int i;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	rrl = view->rrl;
Adam Tkac 05cf27
+	if (rrl == NULL)
Adam Tkac 05cf27
+		return;
Adam Tkac 05cf27
+	view->rrl = NULL;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * Assume the caller takes care of locking the view and anything else.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (rrl->num_logged > 0)
Adam Tkac 05cf27
+		log_stops(rrl, 0, ISC_INT32_MAX, log_buf, sizeof(log_buf));
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	for (i = 0; i < DNS_RRL_QNAMES; ++i) {
Adam Tkac 05cf27
+		if (rrl->qnames[i] == NULL)
Adam Tkac 05cf27
+			break;
Adam Tkac 05cf27
+		isc_mem_put(rrl->mctx, rrl->qnames[i], sizeof(*rrl->qnames[i]));
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (rrl->exempt != NULL)
Adam Tkac 05cf27
+		dns_acl_detach(&rrl->exempt);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	DESTROYLOCK(&rrl->lock);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	while (!ISC_LIST_EMPTY(rrl->blocks)) {
Adam Tkac 05cf27
+		b = ISC_LIST_HEAD(rrl->blocks);
Adam Tkac 05cf27
+		ISC_LIST_UNLINK(rrl->blocks, b, link);
Adam Tkac 05cf27
+		isc_mem_put(rrl->mctx, b, b->size);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	h = rrl->hash;
Adam Tkac 05cf27
+	if (h != NULL)
Adam Tkac 05cf27
+		isc_mem_put(rrl->mctx, h,
Adam Tkac c9b941
+			    sizeof(*h) + (h->length - 1) * sizeof(h->bins[0]));
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	h = rrl->old_hash;
Adam Tkac 05cf27
+	if (h != NULL)
Adam Tkac 05cf27
+		isc_mem_put(rrl->mctx, h,
Adam Tkac c9b941
+			    sizeof(*h) + (h->length - 1) * sizeof(h->bins[0]));
Adam Tkac 05cf27
+
Adam Tkac c9b941
+	isc_mem_putanddetach(&rrl->mctx, rrl, sizeof(*rrl));
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+isc_result_t
Adam Tkac 05cf27
+dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries) {
Adam Tkac 05cf27
+	dns_rrl_t *rrl;
Adam Tkac 05cf27
+	isc_result_t result;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	*rrlp = NULL;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	rrl = isc_mem_get(view->mctx, sizeof(*rrl));
Adam Tkac 05cf27
+	if (rrl == NULL)
Adam Tkac 05cf27
+		return (ISC_R_NOMEMORY);
Adam Tkac 05cf27
+	memset(rrl, 0, sizeof(*rrl));
Adam Tkac c9b941
+	isc_mem_attach(view->mctx, &rrl->mctx);
Adam Tkac 05cf27
+	result = isc_mutex_init(&rrl->lock);
Adam Tkac 05cf27
+	if (result != ISC_R_SUCCESS) {
Adam Tkac c9b941
+		isc_mem_putanddetach(&rrl->mctx, rrl, sizeof(*rrl));
Adam Tkac 05cf27
+		return (result);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	isc_stdtime_get(&rrl->ts_bases[0]);
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	view->rrl = rrl;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	result = expand_entries(rrl, min_entries);
Adam Tkac 05cf27
+	if (result != ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		dns_rrl_view_destroy(view);
Adam Tkac 05cf27
+		return (result);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	result = expand_rrl_hash(rrl, 0);
Adam Tkac 05cf27
+	if (result != ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		dns_rrl_view_destroy(view);
Adam Tkac 05cf27
+		return (result);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	*rrlp = rrl;
Adam Tkac 05cf27
+	return (ISC_R_SUCCESS);
Adam Tkac 05cf27
+}
Tomas Hozza df0cae
diff -r -u lib/dns/view.c-orig lib/dns/view.c
Tomas Hozza df0cae
--- lib/dns/view.c-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ lib/dns/view.c	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -49,6 +49,7 @@
Adam Tkac 05cf27
 #include <dns/masterdump.h>
Adam Tkac 05cf27
 #include <dns/order.h>
Adam Tkac 05cf27
 #include <dns/peer.h>
Adam Tkac 05cf27
+#include <dns/rrl.h>
Adam Tkac 05cf27
 #include <dns/rbt.h>
Adam Tkac 05cf27
 #include <dns/rdataset.h>
Adam Tkac 05cf27
 #include <dns/request.h>
Tomas Hozza df0cae
@@ -184,6 +185,7 @@
Adam Tkac 05cf27
 	view->answeracl_exclude = NULL;
Adam Tkac 05cf27
 	view->denyanswernames = NULL;
Adam Tkac 05cf27
 	view->answernames_exclude = NULL;
Adam Tkac 05cf27
+	view->rrl = NULL;
Adam Tkac 05cf27
 	view->provideixfr = ISC_TRUE;
Adam Tkac 05cf27
 	view->maxcachettl = 7 * 24 * 3600;
Adam Tkac 05cf27
 	view->maxncachettl = 3 * 3600;
Tomas Hozza df0cae
@@ -335,9 +337,11 @@
Adam Tkac 05cf27
 		dns_acache_detach(&view->acache);
Adam Tkac 05cf27
 	}
Adam Tkac 05cf27
 	dns_rpz_view_destroy(view);
Adam Tkac 05cf27
+	dns_rrl_view_destroy(view);
Adam Tkac 05cf27
 #else
Adam Tkac 05cf27
 	INSIST(view->acache == NULL);
Adam Tkac 05cf27
 	INSIST(ISC_LIST_EMPTY(view->rpz_zones));
Adam Tkac 05cf27
+	INSIST(view->rrl == NULL);
Adam Tkac 05cf27
 #endif
Adam Tkac 05cf27
 	if (view->requestmgr != NULL)
Adam Tkac 05cf27
 		dns_requestmgr_detach(&view->requestmgr);
Tomas Hozza df0cae
diff -r -u lib/dns/win32/libdns.def-orig lib/dns/win32/libdns.def
Tomas Hozza df0cae
--- lib/dns/win32/libdns.def-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ lib/dns/win32/libdns.def	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -657,6 +657,9 @@
Adam Tkac 05cf27
 dns_rriterator_next
Adam Tkac 05cf27
 dns_rriterator_nextrrset
Adam Tkac 05cf27
 dns_rriterator_pause
Adam Tkac 05cf27
+dns_rrl
Adam Tkac 05cf27
+dns_rrl_init
Adam Tkac 05cf27
+dns_rrl_view_destroy
Adam Tkac 05cf27
 dns_sdb_putnamedrr
Adam Tkac 05cf27
 dns_sdb_putrdata
Adam Tkac 05cf27
 dns_sdb_putrr
Tomas Hozza df0cae
diff -r -u lib/dns/win32/libdns.dsp-orig lib/dns/win32/libdns.dsp
Tomas Hozza df0cae
--- lib/dns/win32/libdns.dsp-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ lib/dns/win32/libdns.dsp	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -346,6 +346,10 @@
Adam Tkac 05cf27
 # End Source File
Adam Tkac 05cf27
 # Begin Source File
Adam Tkac 05cf27
 
Adam Tkac 05cf27
+SOURCE=..\include\dns\rrl.h
Adam Tkac 05cf27
+# End Source File
Adam Tkac 05cf27
+# Begin Source File
Adam Tkac 05cf27
+
Adam Tkac 05cf27
 SOURCE=..\include\dns\rriterator.h
Adam Tkac 05cf27
 # End Source File
Adam Tkac 05cf27
 # Begin Source File
Tomas Hozza df0cae
@@ -650,6 +654,10 @@
Adam Tkac 05cf27
 # End Source File
Adam Tkac 05cf27
 # Begin Source File
Adam Tkac 05cf27
 
Adam Tkac 05cf27
+SOURCE=..\rrl.c
Adam Tkac 05cf27
+# End Source File
Adam Tkac 05cf27
+# Begin Source File
Adam Tkac 05cf27
+
Adam Tkac 05cf27
 SOURCE=..\rriterator.c
Adam Tkac 05cf27
 # End Source File
Adam Tkac 05cf27
 # Begin Source File
Tomas Hozza df0cae
diff -r -u lib/dns/win32/libdns.mak-orig lib/dns/win32/libdns.mak
Tomas Hozza df0cae
--- lib/dns/win32/libdns.mak-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ lib/dns/win32/libdns.mak	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -184,6 +184,7 @@
Adam Tkac 05cf27
 	-@erase "$(INTDIR)\result.obj"
Adam Tkac 05cf27
 	-@erase "$(INTDIR)\rootns.obj"
Adam Tkac 05cf27
 	-@erase "$(INTDIR)\rpz.obj"
Adam Tkac 05cf27
+	-@erase "$(INTDIR)\rrl.obj"
Adam Tkac 05cf27
 	-@erase "$(INTDIR)\sdb.obj"
Adam Tkac 05cf27
 	-@erase "$(INTDIR)\sdlz.obj"
Adam Tkac 05cf27
 	-@erase "$(INTDIR)\soa.obj"
Tomas Hozza df0cae
@@ -309,6 +310,7 @@
Adam Tkac 05cf27
 	"$(INTDIR)\result.obj" \
Adam Tkac 05cf27
 	"$(INTDIR)\rootns.obj" \
Adam Tkac 05cf27
 	"$(INTDIR)\rpz.obj" \
Adam Tkac 05cf27
+	"$(INTDIR)\rrl.obj" \
Adam Tkac 05cf27
 	"$(INTDIR)\rriterator.obj" \
Adam Tkac 05cf27
 	"$(INTDIR)\sdb.obj" \
Adam Tkac 05cf27
 	"$(INTDIR)\sdlz.obj" \
Tomas Hozza df0cae
@@ -505,6 +507,8 @@
Adam Tkac 05cf27
 	-@erase "$(INTDIR)\rootns.sbr"
Adam Tkac 05cf27
 	-@erase "$(INTDIR)\rpz.obj"
Adam Tkac 05cf27
 	-@erase "$(INTDIR)\rpz.sbr"
Adam Tkac 05cf27
+	-@erase "$(INTDIR)\rrl.obj"
Adam Tkac 05cf27
+	-@erase "$(INTDIR)\rrl.sbr"
Adam Tkac 05cf27
 	-@erase "$(INTDIR)\rriterator.obj"
Adam Tkac 05cf27
 	-@erase "$(INTDIR)\rriterator.sbr"
Adam Tkac 05cf27
 	-@erase "$(INTDIR)\sdb.obj"
Tomas Hozza df0cae
@@ -651,6 +655,7 @@
Adam Tkac 05cf27
 	"$(INTDIR)\result.sbr" \
Adam Tkac 05cf27
 	"$(INTDIR)\rootns.sbr" \
Adam Tkac 05cf27
 	"$(INTDIR)\rpz.sbr" \
Adam Tkac 05cf27
+	"$(INTDIR)\rrl.sbr" \
Adam Tkac 05cf27
 	"$(INTDIR)\rriterator.sbr" \
Adam Tkac 05cf27
 	"$(INTDIR)\sdb.sbr" \
Adam Tkac 05cf27
 	"$(INTDIR)\sdlz.sbr" \
Tomas Hozza df0cae
@@ -748,6 +753,7 @@
Adam Tkac 05cf27
 	"$(INTDIR)\result.obj" \
Adam Tkac 05cf27
 	"$(INTDIR)\rootns.obj" \
Adam Tkac 05cf27
 	"$(INTDIR)\rpz.obj" \
Adam Tkac 05cf27
+	"$(INTDIR)\rrl.obj" \
Adam Tkac 05cf27
 	"$(INTDIR)\rriterator.obj" \
Adam Tkac 05cf27
 	"$(INTDIR)\sdb.obj" \
Adam Tkac 05cf27
 	"$(INTDIR)\sdlz.obj" \
Tomas Hozza df0cae
@@ -1726,6 +1732,24 @@
Adam Tkac 05cf27
 
Tomas Hozza df0cae
 !ENDIF 
Adam Tkac 05cf27
 
Adam Tkac 05cf27
+SOURCE=..\rrl.c
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+!IF  "$(CFG)" == "libdns - Win32 Release"
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+"$(INTDIR)\rrl.obj" : $(SOURCE) "$(INTDIR)"
Adam Tkac 05cf27
+	$(CPP) $(CPP_PROJ) $(SOURCE)
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+!ELSEIF  "$(CFG)" == "libdns - Win32 Debug"
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+"$(INTDIR)\rrl.obj"	"$(INTDIR)\rrl.sbr" : $(SOURCE) "$(INTDIR)"
Adam Tkac 05cf27
+	$(CPP) $(CPP_PROJ) $(SOURCE)
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Tomas Hozza df0cae
+!ENDIF 
Tomas Hozza df0cae
+
Tomas Hozza 60039a
 SOURCE=..\rriterator.c
Tomas Hozza df0cae
 
Tomas Hozza df0cae
 !IF  "$(CFG)" == "libdns - Win32 Release"
Tomas Hozza df0cae
diff -r -u lib/isccfg/namedconf.c-orig lib/isccfg/namedconf.c
Tomas Hozza df0cae
--- lib/isccfg/namedconf.c-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ lib/isccfg/namedconf.c	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -1270,6 +1270,40 @@
Adam Tkac 05cf27
 };
Adam Tkac 05cf27
 
Adam Tkac 05cf27
 
Adam Tkac 05cf27
+/*
Adam Tkac 05cf27
+ * rate-limit
Adam Tkac 05cf27
+ */
Adam Tkac 05cf27
+static cfg_clausedef_t rrl_clauses[] = {
Adam Tkac 05cf27
+	{ "responses-per-second", &cfg_type_uint32, 0 },
Tomas Hozza df0cae
+	{ "referrals-per-second", &cfg_type_uint32, 0 },
Tomas Hozza df0cae
+	{ "nodata-per-second", &cfg_type_uint32, 0 },
Adam Tkac 05cf27
+	{ "nxdomains-per-second", &cfg_type_uint32, 0 },
Tomas Hozza df0cae
+	{ "errors-per-second", &cfg_type_uint32, 0 },
Adam Tkac 05cf27
+	{ "all-per-second", &cfg_type_uint32, 0 },
Adam Tkac 05cf27
+	{ "slip", &cfg_type_uint32, 0 },
Adam Tkac 05cf27
+	{ "window", &cfg_type_uint32, 0 },
Adam Tkac 05cf27
+	{ "log-only", &cfg_type_boolean, 0 },
Adam Tkac 05cf27
+	{ "qps-scale", &cfg_type_uint32, 0 },
Tomas Hozza df0cae
+	{ "ipv4-prefix-length", &cfg_type_uint32, 0 },
Tomas Hozza df0cae
+	{ "ipv6-prefix-length", &cfg_type_uint32, 0 },
Adam Tkac 05cf27
+	{ "exempt-clients", &cfg_type_bracketed_aml, 0 },
Adam Tkac 05cf27
+	{ "max-table-size", &cfg_type_uint32, 0 },
Adam Tkac 05cf27
+	{ "min-table-size", &cfg_type_uint32, 0 },
Adam Tkac 05cf27
+	{ NULL, NULL, 0 }
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static cfg_clausedef_t *rrl_clausesets[] = {
Adam Tkac 05cf27
+	rrl_clauses,
Adam Tkac 05cf27
+	NULL
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+static cfg_type_t cfg_type_rrl = {
Adam Tkac 05cf27
+	"rate-limit", cfg_parse_map, cfg_print_map, cfg_doc_map,
Adam Tkac 05cf27
+	&cfg_rep_map, rrl_clausesets
Adam Tkac 05cf27
+};
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
 /*%
Adam Tkac 05cf27
  * dnssec-lookaside
Adam Tkac 05cf27
  */
Tomas Hozza df0cae
@@ -1423,6 +1457,7 @@
Adam Tkac 05cf27
 	   CFG_CLAUSEFLAG_NOTCONFIGURED },
Adam Tkac 05cf27
 #endif
Adam Tkac 05cf27
 	{ "response-policy", &cfg_type_rpz, 0 },
Adam Tkac 05cf27
+	{ "rate-limit", &cfg_type_rrl, 0 },
Adam Tkac 05cf27
 	{ NULL, NULL, 0 }
Adam Tkac 05cf27
 };
Adam Tkac 05cf27
 
Tomas Hozza df0cae
diff -r -u version-orig version
Tomas Hozza df0cae
--- version-orig	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
+++ version	2004-01-01 00:00:00.000000000 +0000
Tomas Hozza df0cae
@@ -7,6 +7,6 @@
Tomas Hozza df0cae
 DESCRIPTION="(Extended Support Version)"
Tomas Hozza df0cae
 MAJORVER=9
Tomas Hozza df0cae
 MINORVER=9
Tomas Hozza df0cae
-PATCHVER=3
Tomas Hozza df0cae
+PATCHVER=3-rl.150.20
Tomas Hozza df0cae
 RELEASETYPE=
Tomas Hozza df0cae
 RELEASEVER=