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