Adam Tkac c9b941
diff -r -u bin/named/client.c-orig bin/named/client.c
Adam Tkac c9b941
--- bin/named/client.c-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/named/client.c	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -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 |
Adam Tkac c9b941
@@ -1134,6 +1139,49 @@
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);
Adam Tkac 05cf27
+		wouldlog = (ns_g_server->log_queries &&
Adam Tkac 05cf27
+			    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) {
Adam Tkac 05cf27
+				ns_client_log(client, NS_LOGCATEGORY_QUERIES,
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',
Adam Tkac 05cf27
+			 * so don't try.
Adam Tkac 05cf27
+			 * This will counted with dropped queries in the
Adam Tkac 05cf27
+			 * QryDropped counter.
Adam Tkac 05cf27
+			 */
Adam Tkac 05cf27
+			if (!client->view->rrl->log_only) {
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.
Adam Tkac c9b941
diff -r -u bin/named/config.c-orig bin/named/config.c
Adam Tkac c9b941
--- bin/named/config.c-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/named/config.c	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -227,6 +227,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\
Adam Tkac c9b941
diff -r -u bin/named/include/named/query.h-orig bin/named/include/named/query.h
Adam Tkac c9b941
--- bin/named/include/named/query.h-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/named/include/named/query.h	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -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
Adam Tkac c9b941
diff -r -u bin/named/include/named/server.h-orig bin/named/include/named/server.h
Adam Tkac c9b941
--- bin/named/include/named/server.h-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/named/include/named/server.h	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -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
Adam Tkac c9b941
diff -r -u bin/named/query.c-orig bin/named/query.c
Adam Tkac c9b941
--- bin/named/query.c-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/named/query.c	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -5865,6 +5865,105 @@
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.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	if (client->view->rrl != NULL &&
Adam Tkac 05cf27
+	    fname != NULL && dns_name_isabsolute(fname) &&
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];
Adam Tkac 05cf27
+		isc_result_t nc_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);
Adam Tkac 05cf27
+			rrl_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
+			}
Adam Tkac 05cf27
+			rrl_result = DNS_R_NXDOMAIN;
Adam Tkac 05cf27
+		} else if (result == DNS_R_DELEGATION) {
Adam Tkac 05cf27
+			rrl_result = result;
Adam Tkac 05cf27
+		} else {
Adam Tkac 05cf27
+			rrl_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,
Adam Tkac 05cf27
+				     rrl_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
+			 */
Adam Tkac 05cf27
+			if (wouldlog && ns_g_server->log_queries) {
Adam Tkac 05cf27
+				ns_client_log(client, NS_LOGCATEGORY_QUERIES,
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
+			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;
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) &&
Adam Tkac c9b941
@@ -7318,12 +7417,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 {
Adam Tkac c9b941
diff -r -u bin/named/server.c-orig bin/named/server.c
Adam Tkac c9b941
--- bin/named/server.c-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/named/server.c	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -1639,6 +1639,199 @@
Adam Tkac c9b941
 	return (ISC_R_SUCCESS);
Adam Tkac 05cf27
 }
Adam Tkac 05cf27
 
Adam Tkac 05cf27
+#define CHECK_RRL(obj, 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
+
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);
Adam Tkac 05cf27
+		CHECK_RRL(obj, 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
+
Adam Tkac 05cf27
+	i = 0;
Adam Tkac 05cf27
+	obj = NULL;
Adam Tkac 05cf27
+	result = cfg_map_get(map, "responses-per-second", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		i = cfg_obj_asuint32(obj);
Adam Tkac 05cf27
+		CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
Adam Tkac 05cf27
+			  "responses-per-second %d > %d",
Adam Tkac 05cf27
+			  i, DNS_RRL_MAX_RATE);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	rrl->responses_per_second = i;
Adam Tkac 05cf27
+	rrl->scaled_responses_per_second = rrl->responses_per_second;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * The default error rate is the response rate,
Adam Tkac 05cf27
+	 * and so off by default.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	i = rrl->responses_per_second;
Adam Tkac 05cf27
+	obj = NULL;
Adam Tkac 05cf27
+	result = cfg_map_get(map, "errors-per-second", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		i = cfg_obj_asuint32(obj);
Adam Tkac 05cf27
+		CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
Adam Tkac 05cf27
+			  "errors-per-second %d > %d",
Adam Tkac 05cf27
+			  i, DNS_RRL_MAX_RATE);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	rrl->errors_per_second = i;
Adam Tkac 05cf27
+	rrl->scaled_errors_per_second = rrl->errors_per_second;
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * The default NXDOMAIN rate is the response rate,
Adam Tkac 05cf27
+	 * and so off by default.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	i = rrl->responses_per_second;
Adam Tkac 05cf27
+	obj = NULL;
Adam Tkac 05cf27
+	result = cfg_map_get(map, "nxdomains-per-second", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		i = cfg_obj_asuint32(obj);
Adam Tkac 05cf27
+		CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE,
Adam Tkac 05cf27
+			  "nxdomains-per-second %d > %d",
Adam Tkac 05cf27
+			  i, DNS_RRL_MAX_RATE);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	rrl->nxdomains_per_second = i;
Adam Tkac 05cf27
+	rrl->scaled_nxdomains_per_second = rrl->nxdomains_per_second;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	/*
Adam Tkac 05cf27
+	 * The all-per-second rate is off by default.
Adam Tkac 05cf27
+	 */
Adam Tkac 05cf27
+	i = 0;
Adam Tkac 05cf27
+	obj = NULL;
Adam Tkac 05cf27
+	result = cfg_map_get(map, "all-per-second", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		i = cfg_obj_asuint32(obj);
Adam Tkac 05cf27
+		CHECK_RRL(obj, i <= DNS_RRL_MAX_RATE, "all-per-second %d > %d",
Adam Tkac 05cf27
+			  i, DNS_RRL_MAX_RATE);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	rrl->all_per_second = i;
Adam Tkac 05cf27
+	rrl->scaled_all_per_second = rrl->all_per_second;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	i = 2;
Adam Tkac 05cf27
+	obj = NULL;
Adam Tkac 05cf27
+	result = cfg_map_get(map, "slip", &obj);
Adam Tkac 05cf27
+	if (result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+		i = cfg_obj_asuint32(obj);
Adam Tkac 05cf27
+		CHECK_RRL(obj, i <= DNS_RRL_MAX_SLIP,
Adam Tkac 05cf27
+			  "slip %d > %d", i, DNS_RRL_MAX_SLIP);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	rrl->slip = i;
Adam Tkac 05cf27
+	rrl->scaled_slip = rrl->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);
Adam Tkac 05cf27
+		CHECK_RRL(obj, 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);
Adam Tkac 05cf27
+		CHECK_RRL(obj, 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;
Adam Tkac 05cf27
+	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);
Adam Tkac 05cf27
+		CHECK_RRL(obj, i >= 8 && i <= 32,
Adam Tkac 05cf27
+			  "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;
Adam Tkac 05cf27
+	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);
Adam Tkac 05cf27
+		CHECK_RRL(obj, i >= 16 && i <= DNS_RRL_MAX_PREFIX,
Adam Tkac 05cf27
+			  "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);
Adam Tkac 05cf27
+		CHECK_RRL(obj, 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'.
Adam Tkac c9b941
@@ -3043,6 +3236,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:
Adam Tkac c9b941
diff -r -u bin/named/statschannel.c-orig bin/named/statschannel.c
Adam Tkac c9b941
--- bin/named/statschannel.c-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/named/statschannel.c	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -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);
Adam Tkac c9b941
diff -r -u bin/tests/system/README-orig bin/tests/system/README
Adam Tkac c9b941
--- bin/tests/system/README-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/tests/system/README	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -17,6 +17,7 @@
Tomas Hozza c69f33
   nsupdate/	Dynamic update and IXFR tests
Tomas Hozza c69f33
   resolver/     Regression tests for resolver bugs that have been fixed
Tomas Hozza c69f33
 		(not a complete resolver test suite)
Tomas Hozza c69f33
+  rrl/		query rate limiting
Tomas Hozza c69f33
   rpz/		Tests of response policy zone (RPZ) rewriting
Tomas Hozza c69f33
   stub/		Tests of stub zone functionality
Tomas Hozza c69f33
   unknown/	Unknown type and class tests
Adam Tkac c9b941
diff -r -u bin/tests/system/conf.sh.in-orig bin/tests/system/conf.sh.in
Adam Tkac c9b941
--- bin/tests/system/conf.sh.in-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/tests/system/conf.sh.in	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -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
Adam Tkac c9b941
 	 sortlist smartsign staticstub stub tkey tsig tsiggss unknown
Adam Tkac c9b941
 	 upforwd verify views wildcard xfer xferquota zonechecks"
Adam Tkac c9b941
 
Adam Tkac c9b941
diff -r -u bin/tests/system/rrl/clean.sh-orig bin/tests/system/rrl/clean.sh
Adam Tkac c9b941
--- bin/tests/system/rrl/clean.sh-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ 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*
Adam Tkac 05cf27
+rm -f  */named.memstats */named.run */named.stats */log */session.key
Adam Tkac 05cf27
+rm -f ns3/bl*.db */*.jnl */*.core */*.pid
Adam Tkac c9b941
diff -r -u bin/tests/system/rrl/ns1/named.conf-orig bin/tests/system/rrl/ns1/named.conf
Adam Tkac c9b941
--- bin/tests/system/rrl/ns1/named.conf-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ 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";};
Adam Tkac c9b941
diff -r -u bin/tests/system/rrl/ns1/root.db-orig bin/tests/system/rrl/ns1/root.db
Adam Tkac c9b941
--- bin/tests/system/rrl/ns1/root.db-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ 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.
Adam Tkac c9b941
diff -r -u bin/tests/system/rrl/ns2/hints-orig bin/tests/system/rrl/ns2/hints
Adam Tkac c9b941
--- bin/tests/system/rrl/ns2/hints-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ 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
Adam Tkac c9b941
diff -r -u bin/tests/system/rrl/ns2/named.conf-orig bin/tests/system/rrl/ns2/named.conf
Adam Tkac c9b941
--- bin/tests/system/rrl/ns2/named.conf-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/tests/system/rrl/ns2/named.conf	2004-01-01 00:00:00.000000000 +0000
Adam Tkac 05cf27
@@ -0,0 +1,72 @@
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;
Adam Tkac 05cf27
+	    all-per-second 70;
Adam Tkac 05cf27
+	    IPv4-prefix-length 24;
Adam Tkac 05cf27
+	    IPv6-prefix-length 64;
Adam Tkac 05cf27
+	    slip 3;
Adam Tkac 05cf27
+	    /* qps-scale 2; */
Adam Tkac 05cf27
+	    exempt-clients { 10.53.0.7; };
Adam Tkac 05cf27
+	    window 1;
Adam Tkac 05cf27
+	    max-table-size 100;
Adam Tkac 05cf27
+	    min-table-size 2;
Adam Tkac 05cf27
+	};
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"; };
Adam Tkac c9b941
diff -r -u bin/tests/system/rrl/ns2/tld2.db-orig bin/tests/system/rrl/ns2/tld2.db
Adam Tkac c9b941
--- bin/tests/system/rrl/ns2/tld2.db-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/tests/system/rrl/ns2/tld2.db	2004-01-01 00:00:00.000000000 +0000
Adam Tkac 05cf27
@@ -0,0 +1,42 @@
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
+
Adam Tkac c9b941
+a1		A	192.0.2.1
Adam Tkac 05cf27
+
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
+
Adam Tkac 05cf27
+; a8 for all-per-second limit
Adam Tkac c9b941
+$GENERATE 101-180 all$.a8 A 192.0.2.8
Adam Tkac c9b941
diff -r -u bin/tests/system/rrl/ns3/hints-orig bin/tests/system/rrl/ns3/hints
Adam Tkac c9b941
--- bin/tests/system/rrl/ns3/hints-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ 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
Adam Tkac c9b941
diff -r -u bin/tests/system/rrl/ns3/named.conf-orig bin/tests/system/rrl/ns3/named.conf
Adam Tkac c9b941
--- bin/tests/system/rrl/ns3/named.conf-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/tests/system/rrl/ns3/named.conf	2004-01-01 00:00:00.000000000 +0000
Adam Tkac 05cf27
@@ -0,0 +1,34 @@
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;
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"; };
Adam Tkac c9b941
diff -r -u bin/tests/system/rrl/ns3/tld3.db-orig bin/tests/system/rrl/ns3/tld3.db
Adam Tkac c9b941
--- bin/tests/system/rrl/ns3/tld3.db-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ 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
Adam Tkac c9b941
diff -r -u bin/tests/system/rrl/setup.sh-orig bin/tests/system/rrl/setup.sh
Adam Tkac c9b941
--- bin/tests/system/rrl/setup.sh-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ 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
+
Adam Tkac c9b941
diff -r -u bin/tests/system/rrl/tests.sh-orig bin/tests/system/rrl/tests.sh
Adam Tkac c9b941
--- bin/tests/system/rrl/tests.sh-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ bin/tests/system/rrl/tests.sh	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -0,0 +1,225 @@
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
+#set -o noclobber
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
+
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
Adam Tkac 05cf27
+    ARGS="+noadd +noauth +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				\
Adam Tkac 05cf27
+	    | sed -n -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
Adam Tkac 05cf27
+CNT=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
Adam Tkac 05cf27
+	if test $CNT -lt 10; then
Adam Tkac 05cf27
+	    CNT="00$CNT"
Adam Tkac 05cf27
+	else
Adam Tkac 05cf27
+	    if test $CNT -lt 100; then
Adam Tkac 05cf27
+		CNT="0$CNT"
Adam Tkac 05cf27
+	    fi
Adam Tkac 05cf27
+	fi
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 $* &
Adam Tkac 05cf27
+	CNT=`expr $CNT + 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
Adam Tkac 05cf27
+    ADDRS=`ls dig.out-$1-*=$2		2>/dev/null	| wc -l | tr -d ' '`
Adam Tkac 05cf27
+    TC=`ls dig.out-$1-*=TC		2>/dev/null	| wc -l | tr -d ' '`
Adam Tkac 05cf27
+    DROP=`ls dig.out-$1-*=drop		2>/dev/null	| wc -l | tr -d ' '`
Adam Tkac 05cf27
+    NXDOMAIN=`ls dig.out-$1-*=NXDOMAIN	2>/dev/null	| wc -l | tr -d ' '`
Adam Tkac 05cf27
+    SERVFAIL=`ls dig.out-$1-*=SERVFAIL	2>/dev/null	| wc -l | tr -d ' '`
Adam Tkac 05cf27
+    if test $ADDRS -ne "$3"; then
Adam Tkac 05cf27
+	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
Adam Tkac 05cf27
+	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
Adam Tkac 05cf27
+	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
Adam Tkac 05cf27
+	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
Adam Tkac 05cf27
+	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
+
Adam Tkac 05cf27
+#########
Adam Tkac 05cf27
+sec_start
Adam Tkac 05cf27
+
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
Adam Tkac 05cf27
+burst 21 a1.tld2
Adam Tkac 05cf27
+# request 30 different qnames to try a wild card
Adam Tkac 05cf27
+burst 30 'x$CNT.a2.tld2'
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#					IP      TC      drop  NXDOMAIN SERVFAIL
Adam Tkac 05cf27
+# check for 24 results
Adam Tkac 05cf27
+# including the 1 second delay
Adam Tkac c9b941
+ck_result   a1.tld2	192.0.2.1	3	7	14	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
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#########
Adam Tkac 05cf27
+sec_start
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+burst 1 'y$CNT.a3.tld3'; wait; burst 20 'y$CNT.a3.tld3'
Adam Tkac 05cf27
+burst 20 'z$CNT.a4.tld2'
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# Recursion.
Adam Tkac 05cf27
+#   The first answer is counted separately because it is counted against
Adam Tkac 05cf27
+#   the rate limit on recursing to the server for a3.tld3.  The remaining 20
Adam Tkac 05cf27
+#   are counted as local responses from the cache.
Adam Tkac c9b941
+ck_result 'y*.a3.tld3'	192.0.3.3	3	6	12	0	0
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# NXDOMAIN responses are also limited based on the parent name.
Adam Tkac 05cf27
+ck_result 'z*.a4.tld2'	x		0	6	12	2	0
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#########
Adam Tkac 05cf27
+sec_start
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+burst 20 a5.tld2 +tcp
Adam Tkac 05cf27
+burst 20 a6.tld2 -b $ns7
Adam Tkac 05cf27
+burst 20 a7.tld4
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# TCP responses are not rate limited
Adam Tkac c9b941
+ck_result a5.tld2	192.0.2.5	20	0	0	0	0
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# whitelisted client is not rate limited
Adam Tkac c9b941
+ck_result a6.tld2	192.0.2.6	20	0	0	0	0
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# Errors such as SERVFAIL are rate limited.  The numbers are confusing, because
Adam Tkac 05cf27
+#   other rate limiting can be triggered before the SERVFAIL limit is reached.
Adam Tkac c9b941
+ck_result a7.tld4	192.0.2.1	0	6	12	0	2
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+#########
Adam Tkac 05cf27
+sec_start
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+# all-per-second
Adam Tkac 05cf27
+#   The qnames are all unique but the client IP address is constant.
Adam Tkac 05cf27
+CNT=101
Adam Tkac 05cf27
+burst 80 'all$CNT.a8.tld2'
Adam Tkac c9b941
+ck_result 'a*.a8.tld2'	192.0.2.8	70	0	10	0	0
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats
Adam Tkac 05cf27
+ckstats () {
Adam Tkac 05cf27
+    CNT=`sed -n -e "s/[	 ]*\([0-9]*\).responses $1 for rate limits.*/\1/p"  \
Adam Tkac 05cf27
+		ns2/named.stats`
Adam Tkac 05cf27
+    CNT=`expr 0$CNT + 0`
Adam Tkac 05cf27
+    if test "$CNT" -ne $2; then
Adam Tkac 05cf27
+	setret "I:wrong $1 statistics of $CNT instead of $2"
Adam Tkac 05cf27
+    fi
Adam Tkac 05cf27
+}
Adam Tkac 05cf27
+ckstats dropped 77
Adam Tkac 05cf27
+ckstats truncated 35
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
Adam Tkac c9b941
diff -r -u doc/arm/Bv9ARM-book.xml-orig doc/arm/Bv9ARM-book.xml
Adam Tkac c9b941
--- doc/arm/Bv9ARM-book.xml-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ doc/arm/Bv9ARM-book.xml	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -4818,6 +4818,34 @@
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
Adam Tkac 05cf27
+		      is logged in the <command>queries</command> category
Adam Tkac 05cf27
+		      and can be controlled with the
Adam Tkac 05cf27
+		      <command>querylog</command> option.
Adam Tkac 05cf27
+		    </para>
Adam Tkac 05cf27
+		  </entry>
Adam Tkac 05cf27
+		</row>
Adam Tkac 05cf27
 	      
Adam Tkac 05cf27
 	    </tgroup>
Adam Tkac 05cf27
 	  </informaltable>
Adam Tkac c9b941
@@ -5349,6 +5377,21 @@
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>
Adam Tkac 05cf27
+	<optional> errors-per-second <replaceable>number</replaceable> ; </optional>
Adam Tkac 05cf27
+	<optional> nxdomains-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>
Adam Tkac 05cf27
+	<optional> IPv4-prefix-length <replaceable>number</replaceable> ; </optional>
Adam Tkac 05cf27
+	<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> ;
Adam Tkac c9b941
@@ -9859,6 +9902,215 @@
Adam Tkac c9b941
             <command>RPZRewrites</command> statistics.
Adam Tkac c9b941
           </para>
Adam Tkac 05cf27
         </sect3>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	<sect3>
Adam Tkac 05cf27
+	  <title>Rate Limiting</title>
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    Excessive essentially identical UDP <emphasis>responses</emphasis>
Adam Tkac 05cf27
+	    can be discarded by configuring a
Adam Tkac 05cf27
+	    <command>rate-limit</command> clause in an
Adam Tkac 05cf27
+	    <command>options</command> statement.
Adam Tkac 05cf27
+	    This mechanism keeps BIND 9 from being used
Adam Tkac 05cf27
+	    in amplifying reflection denial of service attacks
Adam Tkac 05cf27
+	    as well as partially protecting BIND 9 itself from
Adam Tkac 05cf27
+	    some denial of service attacks.
Adam Tkac 05cf27
+	    Very short truncated responses can be sent to provide
Adam Tkac 05cf27
+	    rate-limited responses to legitimate
Adam Tkac 05cf27
+	    clients within a range of attacked and forged IP addresses,
Adam Tkac 05cf27
+	    Legitimate clients react to truncated response by retrying
Adam Tkac 05cf27
+	    with TCP.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    Rate limiting works by setting
Adam Tkac 05cf27
+	    <command>responses-per-second</command>
Adam Tkac 05cf27
+	    to a number of repetitions per second for responses for a given name
Adam Tkac 05cf27
+	    and record type to a DNS client.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    <command>Responses-per-second</command> is a limit on
Adam Tkac 05cf27
+	    identical responses instead of a limit on all responses or
Adam Tkac 05cf27
+	    even all responses to a single client.
Adam Tkac 05cf27
+	    10 identical responses per second is a generous limit except perhaps
Adam Tkac 05cf27
+	    when many clients are using a single IP address via network
Adam Tkac 05cf27
+	    address translation (NAT).
Adam Tkac 05cf27
+	    The default limit of zero specifies an unbounded limit to turn off
Adam Tkac 05cf27
+	    rate-limiting in a view or to only rate-limit NXDOMAIN or other
Adam Tkac 05cf27
+	    errors.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    The notion of "identical responses"
Adam Tkac 05cf27
+	    and "single DNS client" cannot be simplistic.
Adam Tkac 05cf27
+	    All responses to a CIDR block with prefix
Adam Tkac 05cf27
+	    length specified with <command>IPv4-prefix-length</command>
Adam Tkac 05cf27
+	    (default 24) or <command>IPv6-prefix-length</command>
Adam Tkac 05cf27
+	    (default 56) are assumed to come from a single DNS client.
Adam Tkac 05cf27
+	    Requests for a name that result in DNS NXDOMAIN
Adam Tkac 05cf27
+	    errors are considered identical.
Adam Tkac 05cf27
+	    This controls some attacks using random names, but
Adam Tkac 05cf27
+	    accommodates servers that expect many legitimate NXDOMAIN responses
Adam Tkac 05cf27
+	    such as anti-spam blacklists.
Adam Tkac 05cf27
+	    By default the limit on NXDOMAIN errors is the same as the
Adam Tkac 05cf27
+	    <command>responses-per-second</command> value,
Adam Tkac 05cf27
+	    but it can be set separately with
Adam Tkac 05cf27
+	    <command>nxdomains-per-second</command>.
Adam Tkac 05cf27
+	    All requests for all names or types that result in DNS errors
Adam Tkac 05cf27
+	    such as SERVFAIL and FORMERR (but not NXDOMAIN) are considered
Adam Tkac 05cf27
+	    identical.
Adam Tkac 05cf27
+	    This controls attacks using invalid requests or distant,
Adam Tkac 05cf27
+	    broken authoritative servers.
Adam Tkac 05cf27
+	    By default the limit on errors is the same as the
Adam Tkac 05cf27
+	    <command>responses-per-second</command> value,
Adam Tkac 05cf27
+	    but it can be set separately with
Adam Tkac 05cf27
+	    <command>errors-per-second</command>.
Adam Tkac 05cf27
+	  </para>
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	  <para>
Adam Tkac 05cf27
+	    Rate limiting uses a "credit" or "token bucket" scheme.
Adam Tkac 05cf27
+	    Each identical response has a conceptual account
Adam Tkac 05cf27
+	    that is given <command>responses-per-second</command>,
Adam Tkac 05cf27
+	    <command>errors-per-second</command>, and
Adam Tkac 05cf27
+	    <command>nxdomains-per-second</command> credits every second.
Adam Tkac 05cf27
+	    A DNS request triggering some desired response debits
Adam Tkac 05cf27
+	    the account by one.
Adam Tkac 05cf27
+	    Responses are not sent while the account is negative.
Adam Tkac 05cf27
+	    The account cannot become more positive than
Adam Tkac 05cf27
+	    the per-second limit
Adam Tkac 05cf27
+	    or more negative than <command>window</command>
Adam Tkac 05cf27
+	    times the per-second limit.
Adam Tkac 05cf27
+	    A DNS client that sends requests that are not
Adam Tkac 05cf27
+	    answered can be penalized for up to <command>window</command>
Adam Tkac 05cf27
+	    seconds (default 15).
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.
Adam Tkac 05cf27
+	    This prevents flooding by requesting random.wild.example.com.
Adam Tkac 05cf27
+	    For similar reasons, NXDOMAIN responses are counted and rate
Adam Tkac 05cf27
+	    limited by the valid domain name nearest to the
Adam Tkac 05cf27
+	    query name with an SOA record.
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
Adam Tkac 05cf27
+	    other UDP request to be answered with a small response
Adam Tkac 05cf27
+	    claiming that the response would have been truncated.
Adam Tkac 05cf27
+	    The small size and relative infrequency of the response make
Adam Tkac 05cf27
+	    it unattractive for abuse.
Adam Tkac 05cf27
+	    <command>Slip</command> must be between 0 and 10.
Adam Tkac 05cf27
+	    A value of 0 does not "slip"
Adam Tkac 05cf27
+	    or sends no rate limiting truncated responses.
Adam Tkac 05cf27
+	    Some error responses includinge 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.
Adam Tkac 05cf27
+	    A <command>rate-limit</command> statement in a view replaces
Adam Tkac 05cf27
+	    instead of being merged with 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">
Adam Tkac c9b941
@@ -14587,6 +14839,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>
Adam Tkac c9b941
diff -r -u lib/dns/Makefile.in-orig lib/dns/Makefile.in
Adam Tkac c9b941
--- lib/dns/Makefile.in-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ lib/dns/Makefile.in	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -67,8 +67,8 @@
Adam Tkac c9b941
 		portlist.@O@ private.@O@ \
Adam Tkac c9b941
 		rbt.@O@ rbtdb.@O@ rbtdb64.@O@ rcode.@O@ rdata.@O@ \
Adam Tkac c9b941
 		rdatalist.@O@ rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ \
Adam Tkac c9b941
-		request.@O@ resolver.@O@ result.@O@ rootns.@O@ rpz.@O@ \
Adam Tkac c9b941
-		rriterator.@O@ sdb.@O@ \
Adam Tkac c9b941
+		request.@O@ resolver.@O@ result.@O@ rootns.@O@ \
Adam Tkac c9b941
+		rpz.@O@ rrl.@O@ rriterator.@O@ sdb.@O@ \
Adam Tkac c9b941
 		sdlz.@O@ soa.@O@ ssu.@O@ ssu_external.@O@ \
Adam Tkac c9b941
 		stats.@O@ tcpmsg.@O@ time.@O@ timer.@O@ tkey.@O@ \
Adam Tkac c9b941
 		tsec.@O@ tsig.@O@ ttl.@O@ update.@O@ validator.@O@ \
Adam Tkac c9b941
@@ -95,7 +95,7 @@
Adam Tkac c9b941
 		name.c ncache.c nsec.c nsec3.c order.c peer.c portlist.c \
Adam Tkac c9b941
 		rbt.c rbtdb.c rbtdb64.c rcode.c rdata.c rdatalist.c \
Adam Tkac c9b941
 		rdataset.c rdatasetiter.c rdataslab.c request.c \
Adam Tkac c9b941
-		resolver.c result.c rootns.c rpz.c rriterator.c \
Adam Tkac c9b941
+		resolver.c result.c rootns.c rpz.c rrl.c rriterator.c \
Adam Tkac c9b941
 		sdb.c sdlz.c soa.c ssu.c ssu_external.c \
Adam Tkac c9b941
 		stats.c tcpmsg.c time.c timer.c tkey.c \
Adam Tkac c9b941
 		tsec.c tsig.c ttl.c update.c validator.c \
Adam Tkac c9b941
diff -r -u lib/dns/include/dns/log.h-orig lib/dns/include/dns/log.h
Adam Tkac c9b941
--- lib/dns/include/dns/log.h-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ lib/dns/include/dns/log.h	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -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
Adam Tkac c9b941
diff -r -u lib/dns/include/dns/rrl.h-orig lib/dns/include/dns/rrl.h
Adam Tkac c9b941
--- lib/dns/include/dns/rrl.h-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ lib/dns/include/dns/rrl.h	2004-01-01 00:00:00.000000000 +0000
Adam Tkac 05cf27
@@ -0,0 +1,273 @@
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,
Adam Tkac 05cf27
+	DNS_RRL_RTYPE_DELEGATION,
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
+
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;
Adam Tkac 05cf27
+	int		responses_per_second;
Adam Tkac 05cf27
+	int		errors_per_second;
Adam Tkac 05cf27
+	int		nxdomains_per_second;
Adam Tkac 05cf27
+	int		all_per_second;
Adam Tkac 05cf27
+	int		window;
Adam Tkac 05cf27
+	int		slip;
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
+	int		scaled_responses_per_second;
Adam Tkac 05cf27
+	int		scaled_errors_per_second;
Adam Tkac 05cf27
+	int		scaled_nxdomains_per_second;
Adam Tkac 05cf27
+	int		scaled_all_per_second;
Adam Tkac 05cf27
+	int		scaled_slip;
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 */
Adam Tkac c9b941
diff -r -u lib/dns/include/dns/view.h-orig lib/dns/include/dns/view.h
Adam Tkac c9b941
--- lib/dns/include/dns/view.h-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ 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>
Adam Tkac c9b941
@@ -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;
Adam Tkac c9b941
diff -r -u lib/dns/log.c-orig lib/dns/log.c
Adam Tkac c9b941
--- lib/dns/log.c-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ lib/dns/log.c	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -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
 
Adam Tkac c9b941
diff -r -u lib/dns/rrl.c-orig lib/dns/rrl.c
Adam Tkac c9b941
--- lib/dns/rrl.c-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ lib/dns/rrl.c	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -0,0 +1,1335 @@
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;
Adam Tkac 05cf27
+		     e_old != NULL && e_old->ts_gen == ts_gen;
Adam Tkac c9b941
+		     e_old = ISC_LIST_PREV(e_old, lru), ++i)
Adam Tkac c9b941
+		{
Adam Tkac 05cf27
+			if (e_old->ts_valid)
Adam Tkac 05cf27
+				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;
Adam Tkac c9b941
+	if (rtype == DNS_RRL_RTYPE_QUERY || rtype == DNS_RRL_RTYPE_DELEGATION) {
Adam Tkac c9b941
+		key->s.qclass = qclass & 0xff;
Adam Tkac 05cf27
+		key->s.qtype = qtype;
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
+
Adam Tkac 05cf27
+static inline int
Adam Tkac 05cf27
+response_balance(const dns_rrl_t *rrl, const dns_rrl_entry_t *e, int age) {
Adam Tkac c9b941
+	int balance, rate = 0;
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	balance = e->responses;
Adam Tkac 05cf27
+	if (balance < 0)
Adam Tkac 05cf27
+		switch (e->key.s.rtype) {
Adam Tkac 05cf27
+		case DNS_RRL_RTYPE_QUERY:
Adam Tkac 05cf27
+		case DNS_RRL_RTYPE_DELEGATION:
Adam Tkac 05cf27
+			rate = rrl->scaled_responses_per_second;
Adam Tkac 05cf27
+			break;
Adam Tkac 05cf27
+		case DNS_RRL_RTYPE_NXDOMAIN:
Adam Tkac 05cf27
+			rate = rrl->scaled_nxdomains_per_second;
Adam Tkac 05cf27
+			break;
Adam Tkac 05cf27
+		case DNS_RRL_RTYPE_ERROR:
Adam Tkac 05cf27
+			rate = rrl->scaled_errors_per_second;
Adam Tkac 05cf27
+			break;
Adam Tkac 05cf27
+		case DNS_RRL_RTYPE_ALL:
Adam Tkac 05cf27
+			rate = rrl->scaled_all_per_second;
Adam Tkac 05cf27
+			break;
Adam Tkac 05cf27
+		case DNS_RRL_RTYPE_TCP:
Adam Tkac 05cf27
+			rate = 1;
Adam Tkac 05cf27
+			break;
Adam Tkac 05cf27
+		default:
Adam Tkac 05cf27
+			INSIST(0);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+	balance += 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
+		}
Adam Tkac 05cf27
+		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
+{
Adam Tkac 05cf27
+	int rate, new_rate, *ratep, slip, new_slip, age, log_secs, min;
Adam Tkac c9b941
+	const char *rate_str = NULL;
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
+	 */
Adam Tkac 05cf27
+	switch (e->key.s.rtype) {
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_QUERY:
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_DELEGATION:
Adam Tkac 05cf27
+		rate = rrl->responses_per_second;
Adam Tkac 05cf27
+		ratep = &rrl->scaled_responses_per_second;
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_NXDOMAIN:
Adam Tkac 05cf27
+		rate = rrl->nxdomains_per_second;
Adam Tkac 05cf27
+		ratep = &rrl->scaled_nxdomains_per_second;
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_ERROR:
Adam Tkac 05cf27
+		rate = rrl->errors_per_second;
Adam Tkac 05cf27
+		ratep = &rrl->scaled_errors_per_second;
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_ALL:
Adam Tkac 05cf27
+		rate = rrl->all_per_second;
Adam Tkac 05cf27
+		ratep = &rrl->scaled_all_per_second;
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	default:
Adam Tkac 05cf27
+		INSIST(0);
Adam Tkac 05cf27
+	}
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;
Adam Tkac 05cf27
+		if (*ratep != new_rate) {
Adam Tkac 05cf27
+			if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) {
Adam Tkac 05cf27
+				switch (e->key.s.rtype) {
Adam Tkac 05cf27
+				case DNS_RRL_RTYPE_QUERY:
Adam Tkac 05cf27
+				case DNS_RRL_RTYPE_DELEGATION:
Adam Tkac 05cf27
+					rate_str = "responses-per-second";
Adam Tkac 05cf27
+					break;
Adam Tkac 05cf27
+				case DNS_RRL_RTYPE_NXDOMAIN:
Adam Tkac 05cf27
+					rate_str = "nxdomains-per-second";
Adam Tkac 05cf27
+					break;
Adam Tkac 05cf27
+				case DNS_RRL_RTYPE_ERROR:
Adam Tkac 05cf27
+					rate_str = "errors-per-second";
Adam Tkac 05cf27
+					break;
Adam Tkac 05cf27
+				case DNS_RRL_RTYPE_ALL:
Adam Tkac 05cf27
+					rate_str = "all-per-second";
Adam Tkac 05cf27
+					break;
Adam Tkac c9b941
+				case DNS_RRL_RTYPE_FREE:
Adam Tkac c9b941
+				case DNS_RRL_RTYPE_TCP:
Adam Tkac c9b941
+					INSIST(0);
Adam Tkac 05cf27
+				}
Adam Tkac 05cf27
+				isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+					      DNS_LOGMODULE_REQUEST,
Adam Tkac 05cf27
+					      DNS_RRL_LOG_DEBUG1,
Adam Tkac 05cf27
+					      "%d qps scaled %s by %.2f"
Adam Tkac 05cf27
+					      " from %d to %d",
Adam Tkac 05cf27
+					      (int)qps, rate_str, scale,
Adam Tkac 05cf27
+					      rate, new_rate);
Adam Tkac 05cf27
+			}
Adam Tkac 05cf27
+			rate = new_rate;
Adam Tkac 05cf27
+			*ratep = 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
+	 */
Adam Tkac 05cf27
+	slip = rrl->slip;
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;
Adam Tkac 05cf27
+		if (rrl->scaled_slip != new_slip) {
Adam Tkac 05cf27
+			if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1))
Adam Tkac 05cf27
+				isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
Adam Tkac 05cf27
+					      DNS_LOGMODULE_REQUEST,
Adam Tkac 05cf27
+					      DNS_RRL_LOG_DEBUG1,
Adam Tkac 05cf27
+					      "%d qps scaled slip"
Adam Tkac 05cf27
+					      " by %.2f from %d to %d",
Adam Tkac 05cf27
+					      (int)qps, scale,
Adam Tkac 05cf27
+					      slip, new_slip);
Adam Tkac 05cf27
+			slip = new_slip;
Adam Tkac 05cf27
+			rrl->scaled_slip = 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
+		ADD_LOG_CSTR(&lb, "response");
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_DELEGATION:
Adam Tkac 05cf27
+		ADD_LOG_CSTR(&lb, "referral");
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_NXDOMAIN:
Adam Tkac 05cf27
+		ADD_LOG_CSTR(&lb, "NXDOMAIN response");
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_ERROR:
Adam Tkac 05cf27
+		if (resp_result == ISC_R_SUCCESS) {
Adam Tkac 05cf27
+			ADD_LOG_CSTR(&lb, "error response");
Adam Tkac 05cf27
+		} else {
Adam Tkac 05cf27
+			rstr = isc_result_totext(resp_result);
Adam Tkac c9b941
+			add_log_str(&lb, rstr, strlen(rstr));
Adam Tkac 05cf27
+			ADD_LOG_CSTR(&lb, " response");
Adam Tkac 05cf27
+		}
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	case DNS_RRL_RTYPE_ALL:
Adam Tkac 05cf27
+		ADD_LOG_CSTR(&lb, "all response");
Adam Tkac 05cf27
+		break;
Adam Tkac 05cf27
+	default:
Adam Tkac 05cf27
+		INSIST(0);
Adam Tkac 05cf27
+	}
Adam Tkac 05cf27
+
Adam Tkac 05cf27
+	if (plural)
Adam Tkac 05cf27
+		ADD_LOG_CSTR(&lb, "s to ");
Adam Tkac 05cf27
+	else
Adam Tkac 05cf27
+		ADD_LOG_CSTR(&lb, " 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 ||
Adam Tkac 05cf27
+	    e->key.s.rtype == DNS_RRL_RTYPE_DELEGATION ||
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);
Adam Tkac 05cf27
+			ADD_LOG_CSTR(&lb, " ");
Adam Tkac c9b941
+			(void)dns_rdatatype_totext(e->key.s.qtype, &lb);
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
+	 */
Adam Tkac 05cf27
+	if (resp_result == ISC_R_SUCCESS)
Adam Tkac 05cf27
+		rtype = DNS_RRL_RTYPE_QUERY;
Adam Tkac 05cf27
+	else if (resp_result == DNS_R_DELEGATION)
Adam Tkac 05cf27
+		rtype = DNS_RRL_RTYPE_DELEGATION;
Adam Tkac 05cf27
+	else if (resp_result == DNS_R_NXDOMAIN)
Adam Tkac 05cf27
+		rtype = DNS_RRL_RTYPE_NXDOMAIN;
Adam Tkac 05cf27
+	else
Adam Tkac 05cf27
+		rtype = DNS_RRL_RTYPE_ERROR;
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
+
Adam Tkac 05cf27
+	if (rrl->all_per_second != 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)
Adam Tkac 05cf27
+		make_log_buf(rrl, e, rrl->log_only ? "would " : NULL,
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
+}
Adam Tkac c9b941
diff -r -u lib/dns/view.c-orig lib/dns/view.c
Adam Tkac c9b941
--- lib/dns/view.c-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ 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>
Adam Tkac c9b941
@@ -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;
Adam Tkac c9b941
@@ -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);
Adam Tkac c9b941
diff -r -u lib/dns/win32/libdns.def-orig lib/dns/win32/libdns.def
Adam Tkac c9b941
--- lib/dns/win32/libdns.def-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ lib/dns/win32/libdns.def	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -656,6 +656,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
Adam Tkac c9b941
diff -r -u lib/dns/win32/libdns.dsp-orig lib/dns/win32/libdns.dsp
Adam Tkac c9b941
--- lib/dns/win32/libdns.dsp-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ lib/dns/win32/libdns.dsp	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -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
Adam Tkac c9b941
@@ -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
Adam Tkac c9b941
diff -r -u lib/dns/win32/libdns.mak-orig lib/dns/win32/libdns.mak
Adam Tkac c9b941
--- lib/dns/win32/libdns.mak-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ lib/dns/win32/libdns.mak	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -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"
Adam Tkac c9b941
@@ -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" \
Adam Tkac c9b941
@@ -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"
Adam Tkac c9b941
@@ -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" \
Adam Tkac c9b941
@@ -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" \
Adam Tkac c9b941
@@ -1726,6 +1732,24 @@
Adam Tkac 05cf27
 
Adam Tkac c9b941
 !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
+
Adam Tkac c9b941
+!ENDIF 
Adam Tkac c9b941
+
Tomas Hozza c69f33
 SOURCE=..\rriterator.c
Adam Tkac c9b941
 
Adam Tkac c9b941
 !IF  "$(CFG)" == "libdns - Win32 Release"
Adam Tkac c9b941
diff -r -u lib/isccfg/namedconf.c-orig lib/isccfg/namedconf.c
Adam Tkac c9b941
--- lib/isccfg/namedconf.c-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ lib/isccfg/namedconf.c	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -1264,6 +1264,39 @@
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 },
Adam Tkac 05cf27
+	{ "errors-per-second", &cfg_type_uint32, 0 },
Adam Tkac 05cf27
+	{ "nxdomains-per-second", &cfg_type_uint32, 0 },
Adam Tkac 05cf27
+	{ "responses-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 },
Adam Tkac 05cf27
+	{ "IPv4-prefix-length", &cfg_type_uint32, 0 },
Adam Tkac 05cf27
+	{ "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
  */
Adam Tkac c9b941
@@ -1417,6 +1450,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
 
Adam Tkac c9b941
diff -r -u version-orig version
Adam Tkac c9b941
--- version-orig	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
+++ version	2004-01-01 00:00:00.000000000 +0000
Adam Tkac c9b941
@@ -6,6 +6,6 @@
Adam Tkac c9b941
 PRODUCT=BIND
Adam Tkac 05cf27
 MAJORVER=9
Adam Tkac 05cf27
 MINORVER=9
Adam Tkac c9b941
-PATCHVER=3
Adam Tkac c9b941
+PATCHVER=3-rl.105.03
Adam Tkac c9b941
 RELEASETYPE=rc
Adam Tkac c9b941
 RELEASEVER=1