]> git.ipfire.org Git - thirdparty/bind9.git/commitdiff
[v9_9] backport RRL to 9.9.x
authorEvan Hunt <each@isc.org>
Fri, 7 Jun 2013 19:47:11 +0000 (12:47 -0700)
committerEvan Hunt <each@isc.org>
Fri, 7 Jun 2013 19:47:11 +0000 (12:47 -0700)
This incorporates the following changes, plus a new configure
option "--enable-rrl" to turn them on:

3575. [func] Changed the logging category for RRL events from
'queries' to 'query-errors'. [RT #33540]

3554. [bug] RRL failed to correctly rate-limit upward
referrals and failed to count dropped error
responses in the statistics. [RT #33225]

3545. [bug] RRL slip behavior was incorrect when set to 1.
[RT #33111]

3518. [bug] Increase the size of dns_rrl_key.s.rtype by one bit
so that all dns_rrl_rtype_t enum values fit regardless
of whether it is teated as signed or unsigned by
the compiler. [RT #32792]

3494. [func] DNS RRL: Blunt the impact of DNS reflection and
amplification attacks by rate-limiting substantially-
identical responses. To enable, use "configure
--enable-rrl". [RT #28130]

41 files changed:
CHANGES
bin/named/client.c
bin/named/config.c
bin/named/include/named/query.h
bin/named/include/named/server.h
bin/named/query.c
bin/named/server.c
bin/named/statschannel.c
bin/tests/system/Makefile.in
bin/tests/system/README
bin/tests/system/conf.sh.in
bin/tests/system/rrl/.gitignore [new file with mode: 0644]
bin/tests/system/rrl/Makefile.in [new file with mode: 0644]
bin/tests/system/rrl/clean.sh [new file with mode: 0644]
bin/tests/system/rrl/ns1/named.conf [new file with mode: 0644]
bin/tests/system/rrl/ns1/root.db [new file with mode: 0644]
bin/tests/system/rrl/ns2/hints [new file with mode: 0644]
bin/tests/system/rrl/ns2/named.conf [new file with mode: 0644]
bin/tests/system/rrl/ns2/tld2.db [new file with mode: 0644]
bin/tests/system/rrl/ns3/hints [new file with mode: 0644]
bin/tests/system/rrl/ns3/named.conf [new file with mode: 0644]
bin/tests/system/rrl/ns3/tld3.db [new file with mode: 0644]
bin/tests/system/rrl/prereq.sh [new file with mode: 0644]
bin/tests/system/rrl/rrl.c [new file with mode: 0644]
bin/tests/system/rrl/setup.sh [new file with mode: 0644]
bin/tests/system/rrl/tests.sh [new file with mode: 0644]
config.h.in
configure
configure.in
doc/arm/Bv9ARM-book.xml
lib/dns/Makefile.in
lib/dns/include/dns/log.h
lib/dns/include/dns/rrl.h [new file with mode: 0644]
lib/dns/include/dns/view.h
lib/dns/log.c
lib/dns/rrl.c [new file with mode: 0644]
lib/dns/view.c
lib/dns/win32/libdns.def
lib/dns/win32/libdns.dsp
lib/dns/win32/libdns.mak
lib/isccfg/namedconf.c

diff --git a/CHANGES b/CHANGES
index 4b2c8fcad9ef9147c62a993f545f68df4dcaf406..027447fde8446cf3fffbced90618257838b8c059 100644 (file)
--- a/CHANGES
+++ b/CHANGES
@@ -28,6 +28,9 @@
 
 3576.  [bug]           Address a shutdown race when validating. [RT #33573]
 
+3575.  [func]          Changed the logging category for RRL events from
+                       'queries' to 'query-errors'. [RT #33540]
+
 3574.  [doc]           The 'hostname' keyword was missing from server-id
                        description in the named.conf man page. [RT #33476]
 
 
 3566.  [func]          Log when forwarding updates to master. [RT #33240]
 
+3554.  [bug]           RRL failed to correctly rate-limit upward
+                       referrals and failed to count dropped error
+                       responses in the statistics. [RT #33225]
+
+3545.  [bug]           RRL slip behavior was incorrect when set to 1.
+                       [RT #33111]
+
+3518.  [bug]           Increase the size of dns_rrl_key.s.rtype by one bit
+                       so that all dns_rrl_rtype_t enum values fit regardless
+                       of whether it is teated as signed or unsigned by
+                       the compiler. [RT #32792]
+
+3494.  [func]          DNS RRL: Blunt the impact of DNS reflection and
+                       amplification attacks by rate-limiting substantially-
+                       identical responses. To enable, use "configure
+                        --enable-rrl". [RT #28130]
+
        --- 9.9.3 released ---
 
 3568.  [cleanup]       Add a product description line to the version file,
index 933abc7631e27f852515da466e42f9583bbb1e1a..35f59c53e5cdb7953d7d9f59263d165b1412dae4 100644 (file)
@@ -994,6 +994,13 @@ ns_client_send(ns_client_t *client) {
        }
        if (result != ISC_R_SUCCESS)
                goto done;
+#ifdef USE_RRL
+       /*
+        * Stop after the question if TC was set for rate limiting.
+        */
+       if ((client->message->flags & DNS_MESSAGEFLAG_TC) != 0)
+               goto renderend;
+#endif /* USE_RRL */
        result = dns_message_rendersection(client->message,
                                           DNS_SECTION_ANSWER,
                                           DNS_MESSAGERENDER_PARTIAL |
@@ -1133,6 +1140,53 @@ ns_client_error(ns_client_t *client, isc_result_t result) {
        }
 #endif
 
+#ifdef USE_RRL
+       /*
+        * Try to rate limit error responses.
+        */
+       if (client->view != NULL && client->view->rrl != NULL) {
+               isc_boolean_t wouldlog;
+               char log_buf[DNS_RRL_LOG_BUF_LEN];
+               dns_rrl_result_t rrl_result;
+
+               INSIST(rcode != dns_rcode_noerror &&
+                      rcode != dns_rcode_nxdomain);
+               wouldlog = isc_log_wouldlog(ns_g_lctx, DNS_RRL_LOG_DROP);
+               rrl_result = dns_rrl(client->view, &client->peeraddr,
+                                    TCP_CLIENT(client),
+                                    dns_rdataclass_in, dns_rdatatype_none,
+                                    NULL, result, client->now,
+                                    wouldlog, log_buf, sizeof(log_buf));
+               if (rrl_result != DNS_RRL_RESULT_OK) {
+                       /*
+                        * Log dropped errors in the query category
+                        * so that they are not lost in silence.
+                        * Starts of rate-limited bursts are logged in
+                        * NS_LOGCATEGORY_RRL.
+                        */
+                       if (wouldlog) {
+                               ns_client_log(client,
+                                             NS_LOGCATEGORY_QUERY_EERRORS,
+                                             NS_LOGMODULE_CLIENT,
+                                             DNS_RRL_LOG_DROP,
+                                             "%s", log_buf);
+                       }
+                       /*
+                        * Some error responses cannot be 'slipped',
+                        * so don't try to slip any error responses.
+                        */
+                       if (!client->view->rrl->log_only) {
+                               isc_stats_increment(ns_g_server->nsstats,
+                                               dns_nsstatscounter_ratedropped);
+                               isc_stats_increment(ns_g_server->nsstats,
+                                               dns_nsstatscounter_dropped);
+                               ns_client_next(client, DNS_R_DROP);
+                               return;
+                       }
+               }
+       }
+#endif /* USE_RRL */
+
        /*
         * Message may be an in-progress reply that we had trouble
         * with, in which case QR will be set.  We need to clear QR before
index fd136b18167358747850e74da5b9aa8fcc30c13c..92cf7bf7537b6d9b33bb999f80811be2718f5e9d 100644 (file)
@@ -227,8 +227,17 @@ view \"_bind\" chaos {\n\
        recursion no;\n\
        notify no;\n\
        allow-new-zones no;\n\
-\n\
-       zone \"version.bind\" chaos {\n\
+"
+#ifdef USE_RRL
+"      # Prevent use of this zone in DNS amplified reflection DoS attacks\n\
+       rate-limit {\n\
+               responses-per-second 3;\n\
+               slip 0;\n\
+               min-table-size 10;\n\
+       };\n\
+"
+#endif /* USE_RRL */
+"      zone \"version.bind\" chaos {\n\
                type master;\n\
                database \"_builtin version\";\n\
        };\n\
index 6dfe96bc9d4dbe162987c02848ee1120dfb2febf..38ae081f71e1904eaf12c4306b312191e5aa68c1 100644 (file)
@@ -86,6 +86,10 @@ struct ns_query {
 #define NS_QUERYATTR_DNS64             0x4000
 #define NS_QUERYATTR_DNS64EXCLUDE      0x8000
 
+#ifdef USE_RRL
+#define NS_QUERYATTR_RRL_CHECKED       0x10000
+#endif /* USE_RRL */
+
 
 isc_result_t
 ns_query_init(ns_client_t *client);
index 3ba0c64a712d874bad43f4a255709cd981699a06..ff0bfd3bef61475c6ae1d5e53fbbec52e242eb49 100644 (file)
@@ -167,7 +167,14 @@ enum {
 
        dns_nsstatscounter_rpz_rewrites = 36,
 
+#ifdef USE_RRL
+       dns_nsstatscounter_ratedropped = 37,
+       dns_nsstatscounter_rateslipped = 38,
+
+       dns_nsstatscounter_max = 39
+#else /* USE_RRL */
        dns_nsstatscounter_max = 37
+#endif /* USE_RRL */
 };
 
 void
index 5093cb243da401c5ba395bbc159adf46fd4506df..6917f076fe1ab8342f71b90ff120d98b5eab5153 100644 (file)
@@ -193,7 +193,7 @@ inc_stats(ns_client_t *client, isc_statscounter_t counter) {
 #ifdef NEWSTATS
        /* Do query type statistics
         *
-        * We only increment per-type if we're using the authoriative
+        * We only increment per-type if we're using the authoritative
         * answer counter, preventing double-counting.
         */
        if (counter == dns_nsstatscounter_authans) {
@@ -5865,6 +5865,120 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
  resume:
        CTRACE("query_find: resume");
 
+#ifdef USE_RRL
+       /*
+        * Rate limit these responses to this client.
+        */
+       if (client->view->rrl != NULL &&
+           ((fname != NULL && dns_name_isabsolute(fname)) ||
+            (result == ISC_R_NOTFOUND && !RECURSIONOK(client))) &&
+           (client->query.attributes & NS_QUERYATTR_RRL_CHECKED) == 0) {
+               dns_rdataset_t nc_rdataset;
+               isc_boolean_t wouldlog;
+               char log_buf[DNS_RRL_LOG_BUF_LEN];
+               isc_result_t nc_result;
+               dns_rrl_result_t rrl_result;
+
+               client->query.attributes |= NS_QUERYATTR_RRL_CHECKED;
+
+               wouldlog = isc_log_wouldlog(ns_g_lctx, DNS_RRL_LOG_DROP);
+               tname = fname;
+               if (result == DNS_R_NXDOMAIN) {
+                       /*
+                        * Use the database origin name to rate limit NXDOMAIN
+                        */
+                       if (db != NULL)
+                               tname = dns_db_origin(db);
+                       rrl_result = result;
+               } else if (result == DNS_R_NCACHENXDOMAIN &&
+                          rdataset != NULL &&
+                          dns_rdataset_isassociated(rdataset) &&
+                          (rdataset->attributes &
+                           DNS_RDATASETATTR_NEGATIVE) != 0) {
+                       /*
+                        * Try to use owner name in the negative cache SOA.
+                        */
+                       dns_fixedname_init(&fixed);
+                       dns_rdataset_init(&nc_rdataset);
+                       for (nc_result = dns_rdataset_first(rdataset);
+                            nc_result == ISC_R_SUCCESS;
+                            nc_result = dns_rdataset_next(rdataset)) {
+                               dns_ncache_current(rdataset,
+                                                  dns_fixedname_name(&fixed),
+                                                  &nc_rdataset);
+                               if (nc_rdataset.type == dns_rdatatype_soa) {
+                                       dns_rdataset_disassociate(&nc_rdataset);
+                                       tname = dns_fixedname_name(&fixed);
+                                       break;
+                               }
+                               dns_rdataset_disassociate(&nc_rdataset);
+                       }
+                       rrl_result = DNS_R_NXDOMAIN;
+               } else if (result == DNS_R_NXRRSET ||
+                          result == DNS_R_EMPTYNAME) {
+                       rrl_result = DNS_R_NXRRSET;
+               } else if (result == DNS_R_DELEGATION) {
+                       rrl_result = result;
+               } else if (result == ISC_R_NOTFOUND) {
+                       /*
+                        * Handle referral to ".", including when recursion
+                        * is off or not requested and the hints have not
+                        * been loaded or we have "additional-from-cache no".
+                        */
+                       tname = dns_rootname;
+                       rrl_result = DNS_R_DELEGATION;
+               } else {
+                       rrl_result = ISC_R_SUCCESS;
+               }
+               rrl_result = dns_rrl(client->view, &client->peeraddr,
+                                    ISC_TF((client->attributes
+                                            & NS_CLIENTATTR_TCP) != 0),
+                                    client->message->rdclass, qtype, tname,
+                                    rrl_result, client->now,
+                                    wouldlog, log_buf, sizeof(log_buf));
+               if (rrl_result != DNS_RRL_RESULT_OK) {
+                       /*
+                        * Log dropped or slipped responses in the query
+                        * category so that requests are not silently lost.
+                        * Starts of rate-limited bursts are logged in
+                        * DNS_LOGCATEGORY_RRL.
+                        *
+                        * Dropped responses are counted with dropped queries
+                        * in QryDropped while slipped responses are counted
+                        * with other truncated responses in RespTruncated.
+                        */
+                       if (wouldlog) {
+                               ns_client_log(client,
+                                             NS_LOGCATEGORY_QUERY_EERRORS,
+                                             NS_LOGMODULE_QUERY,
+                                             DNS_RRL_LOG_DROP,
+                                             "%s", log_buf);
+                       }
+                       if (!client->view->rrl->log_only) {
+                               if (rrl_result == DNS_RRL_RESULT_DROP) {
+                                       /*
+                                        * These will also be counted in
+                                        * dns_nsstatscounter_dropped
+                                        */
+                                       inc_stats(client,
+                                               dns_nsstatscounter_ratedropped);
+                                       QUERY_ERROR(DNS_R_DROP);
+                               } else {
+                                       /*
+                                        * These will also be counted in
+                                        * dns_nsstatscounter_truncatedresp
+                                        */
+                                       inc_stats(client,
+                                               dns_nsstatscounter_rateslipped);
+                                       client->message->flags |=
+                                               DNS_MESSAGEFLAG_TC;
+                               }
+                               goto cleanup;
+                       }
+               }
+       }
+#endif /* USE_RRL */
+
        if (!ISC_LIST_EMPTY(client->view->rpz_zones) &&
            (RECURSIONOK(client) || !client->view->rpz_recursive_only) &&
            rpz_ck_dnssec(client, result, rdataset, sigrdataset) &&
@@ -7317,13 +7431,21 @@ query_find(ns_client_t *client, dns_fetchevent_t *event, dns_rdatatype_t qtype)
                goto restart;
        }
 
+#ifdef USE_RRL
        if (eresult != ISC_R_SUCCESS &&
-           (!PARTIALANSWER(client) || WANTRECURSION(client))) {
+           (!PARTIALANSWER(client) || WANTRECURSION(client)
+            || eresult == DNS_R_DROP))
+#else /* USE_RRL */
+       if (eresult != ISC_R_SUCCESS &&
+           (!PARTIALANSWER(client) || WANTRECURSION(client)))
+#endif /* USE_RRL */
+       {
                if (eresult == DNS_R_DUPLICATE || eresult == DNS_R_DROP) {
                        /*
                         * This was a duplicate query that we are
-                        * recursing on.  Don't send a response now.
-                        * The original query will still cause a response.
+                        * recursing on or the result of rate limiting.
+                        * Don't send a response now for a duplicate query,
+                        * because the original will still cause a response.
                         */
                        query_next(client, eresult);
                } else {
index a770742a3ebcc27b27368f77bb415f559208dad6..f28504758affb731c8425d328c20e2d6fdf4d421 100644 (file)
@@ -1639,6 +1639,170 @@ configure_rpz(dns_view_t *view, const cfg_listelt_t *element,
        return (ISC_R_SUCCESS);
 }
 
+#ifdef USE_RRL
+#define CHECK_RRL(cond, pat, val1, val2)                               \
+       do {                                                            \
+               if (!(cond)) {                                          \
+                       cfg_obj_log(obj, ns_g_lctx, ISC_LOG_ERROR,      \
+                                   pat, val1, val2);                   \
+                       result = ISC_R_RANGE;                           \
+                       goto cleanup;                                   \
+                   }                                                   \
+       } while (0)
+
+#define CHECK_RRL_RATE(rate, def, max_rate, name)                      \
+       do {                                                            \
+               obj = NULL;                                             \
+               rrl->rate.str = name;                                   \
+               result = cfg_map_get(map, name, &obj);                  \
+               if (result == ISC_R_SUCCESS) {                          \
+                       rrl->rate.r = cfg_obj_asuint32(obj);            \
+                       CHECK_RRL(rrl->rate.r <= max_rate,              \
+                                 name" %d > %d",                       \
+                                 rrl->rate.r, max_rate);               \
+               } else {                                                \
+                       rrl->rate.r = def;                              \
+               }                                                       \
+               rrl->rate.scaled = rrl->rate.r;                         \
+       } while (0)
+
+static isc_result_t
+configure_rrl(dns_view_t *view, const cfg_obj_t *config, const cfg_obj_t *map) {
+       const cfg_obj_t *obj;
+       dns_rrl_t *rrl;
+       isc_result_t result;
+       int min_entries, i, j;
+
+       /*
+        * Most DNS servers have few clients, but intentinally open
+        * recursive and authoritative servers often have many.
+        * So start with a small number of entries unless told otherwise
+        * to reduce cold-start costs.
+        */
+       min_entries = 500;
+       obj = NULL;
+       result = cfg_map_get(map, "min-table-size", &obj);
+       if (result == ISC_R_SUCCESS) {
+               min_entries = cfg_obj_asuint32(obj);
+               if (min_entries < 1)
+                       min_entries = 1;
+       }
+       result = dns_rrl_init(&rrl, view, min_entries);
+       if (result != ISC_R_SUCCESS)
+               return (result);
+
+       i = ISC_MAX(20000, min_entries);
+       obj = NULL;
+       result = cfg_map_get(map, "max-table-size", &obj);
+       if (result == ISC_R_SUCCESS) {
+               i = cfg_obj_asuint32(obj);
+               CHECK_RRL(i >= min_entries,
+                         "max-table-size %d < min-table-size %d",
+                         i, min_entries);
+       }
+       rrl->max_entries = i;
+
+       CHECK_RRL_RATE(responses_per_second, 0, DNS_RRL_MAX_RATE,
+                      "responses-per-second");
+       CHECK_RRL_RATE(referrals_per_second,
+                      rrl->responses_per_second.r, DNS_RRL_MAX_RATE,
+                      "referrals-per-second");
+       CHECK_RRL_RATE(nodata_per_second,
+                      rrl->responses_per_second.r, DNS_RRL_MAX_RATE,
+                      "nodata-per-second");
+       CHECK_RRL_RATE(nxdomains_per_second,
+                      rrl->responses_per_second.r, DNS_RRL_MAX_RATE,
+                      "nxdomains-per-second");
+       CHECK_RRL_RATE(errors_per_second,
+                      rrl->responses_per_second.r, DNS_RRL_MAX_RATE,
+                      "errors-per-second");
+
+       CHECK_RRL_RATE(all_per_second, 0, DNS_RRL_MAX_RATE,
+                      "all-per-second");
+
+       CHECK_RRL_RATE(slip, 2, DNS_RRL_MAX_SLIP,
+                      "slip");
+
+       i = 15;
+       obj = NULL;
+       result = cfg_map_get(map, "window", &obj);
+       if (result == ISC_R_SUCCESS) {
+               i = cfg_obj_asuint32(obj);
+               CHECK_RRL(i >= 1 && i <= DNS_RRL_MAX_WINDOW,
+                         "window %d < 1 or > %d", i, DNS_RRL_MAX_WINDOW);
+       }
+       rrl->window = i;
+
+       i = 0;
+       obj = NULL;
+       result = cfg_map_get(map, "qps-scale", &obj);
+       if (result == ISC_R_SUCCESS) {
+               i = cfg_obj_asuint32(obj);
+               CHECK_RRL(i >= 1, "invalid 'qps-scale %d'%s", i, "");
+       }
+       rrl->qps_scale = i;
+       rrl->qps = 1.0;
+
+       i = 24;
+       obj = NULL;
+       result = cfg_map_get(map, "ipv4-prefix-length", &obj);
+       if (result == ISC_R_SUCCESS) {
+               i = cfg_obj_asuint32(obj);
+               CHECK_RRL(i >= 8 && i <= 32,
+                         "invalid 'ipv4-prefix-length %d'%s", i, "");
+       }
+       rrl->ipv4_prefixlen = i;
+       if (i == 32)
+               rrl->ipv4_mask = 0xffffffff;
+       else
+               rrl->ipv4_mask = htonl(0xffffffff << (32-i));
+
+       i = 56;
+       obj = NULL;
+       result = cfg_map_get(map, "ipv6-prefix-length", &obj);
+       if (result == ISC_R_SUCCESS) {
+               i = cfg_obj_asuint32(obj);
+               CHECK_RRL(i >= 16 && i <= DNS_RRL_MAX_PREFIX,
+                         "ipv6-prefix-length %d < 16 or > %d",
+                         i, DNS_RRL_MAX_PREFIX);
+       }
+       rrl->ipv6_prefixlen = i;
+       for (j = 0; j < 4; ++j) {
+               if (i <= 0) {
+                       rrl->ipv6_mask[j] = 0;
+               } else if (i < 32) {
+                       rrl->ipv6_mask[j] = htonl(0xffffffff << (32-i));
+               } else {
+                       rrl->ipv6_mask[j] = 0xffffffff;
+               }
+               i -= 32;
+       }
+
+       obj = NULL;
+       result = cfg_map_get(map, "exempt-clients", &obj);
+       if (result == ISC_R_SUCCESS) {
+               result = cfg_acl_fromconfig(obj, config, ns_g_lctx,
+                                           ns_g_aclconfctx, ns_g_mctx,
+                                           0, &rrl->exempt);
+               CHECK_RRL(result == ISC_R_SUCCESS,
+                         "invalid %s%s", "address match list", "");
+       }
+
+       obj = NULL;
+       result = cfg_map_get(map, "log-only", &obj);
+       if (result == ISC_R_SUCCESS && cfg_obj_asboolean(obj))
+               rrl->log_only = ISC_TRUE;
+       else
+               rrl->log_only = ISC_FALSE;
+
+       return (ISC_R_SUCCESS);
+
+ cleanup:
+       dns_rrl_view_destroy(view);
+       return (result);
+}
+#endif /* USE_RRL */
+
 /*
  * Configure 'view' according to 'vconfig', taking defaults from 'config'
  * where values are missing in 'vconfig'.
@@ -3043,6 +3207,16 @@ configure_view(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig,
                }
        }
 
+#ifdef USE_RRL
+       obj = NULL;
+       result = ns_config_get(maps, "rate-limit", &obj);
+       if (result == ISC_R_SUCCESS) {
+               result = configure_rrl(view, config, obj);
+               if (result != ISC_R_SUCCESS)
+                       goto cleanup;
+       }
+#endif /* USE_RRL */
+
        result = ISC_R_SUCCESS;
 
  cleanup:
index c78f78044c08f66eda5d63f0428b72c814d32c2e..b1372e30a67cbf41bd5c8f9f934a52d8cbe9e9bc 100644 (file)
@@ -208,6 +208,12 @@ init_desc(void) {
                       "UpdateBadPrereq");
        SET_NSSTATDESC(rpz_rewrites, "response policy zone rewrites",
                       "RPZRewrites");
+#ifdef USE_RRL
+       SET_NSSTATDESC(ratedropped, "responses dropped for rate limits",
+                      "RateDropped");
+       SET_NSSTATDESC(rateslipped, "responses truncated for rate limits",
+                      "RateSlipped");
+#endif /* USE_RRL */
        INSIST(i == dns_nsstatscounter_max);
 
        /* Initialize resolver statistics */
index 1f9f41fdd0224ffe29109483c585b4d9c7184200..724db3f26dc01a40da46ad7bdf19daf702c2543b 100644 (file)
@@ -21,7 +21,8 @@ top_srcdir =  @top_srcdir@
 
 @BIND9_MAKE_INCLUDES@
 
-SUBDIRS =      dlzexternal filter-aaaa lwresd rpz rsabigexponent tkey tsiggss
+SUBDIRS =      dlzexternal filter-aaaa lwresd rpz rrl \
+               rsabigexponent tkey tsiggss
 TARGETS =
 
 @BIND9_MAKE_RULES@
index dbca6083dedf8b4ac3ea0c385776394013cce467..229c887abb7340b0b53358f66ea136a9045b0c7a 100644 (file)
@@ -17,6 +17,7 @@ involving a different DNS setup.  They are:
   nsupdate/    Dynamic update and IXFR tests
   resolver/     Regression tests for resolver bugs that have been fixed
                (not a complete resolver test suite)
+  rrl/         query rate limiting
   rpz/         Tests of response policy zone (RPZ) rewriting
   stub/                Tests of stub zone functionality
   unknown/     Unknown type and class tests
index 9246b52f09652cfae4a1bca6f65a5d2dcb59b98a..447aaa645c7d416f39564cae2eec078d957b095e 100644 (file)
@@ -62,7 +62,7 @@ SUBDIRS="acl additional allow_query addzone autosign builtin
          database dlv dlvauto dlz dlzexternal dname dns64 dnssec ecdsa
          formerr forward glue gost ixfr inline limits logfileconfig
          lwresd masterfile masterformat metadata notify nsupdate pending
-        pkcs11 redirect resolver rndc rpz rrsetorder rsabigexponent
+        pkcs11 redirect resolver rndc rpz rrl rrsetorder rsabigexponent
         smartsign sortlist spf staticstub stub tkey tsig tsiggss unknown
         upforwd verify views wildcard xfer xferquota zero zonechecks"
 
diff --git a/bin/tests/system/rrl/.gitignore b/bin/tests/system/rrl/.gitignore
new file mode 100644 (file)
index 0000000..16b5796
--- /dev/null
@@ -0,0 +1 @@
+flood
diff --git a/bin/tests/system/rrl/Makefile.in b/bin/tests/system/rrl/Makefile.in
new file mode 100644 (file)
index 0000000..1c737dd
--- /dev/null
@@ -0,0 +1,53 @@
+# Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+srcdir =       @srcdir@
+VPATH =                @srcdir@
+top_srcdir =   @top_srcdir@
+
+@BIND9_VERSION@
+
+@BIND9_MAKE_INCLUDES@
+
+CINCLUDES =    ${ISC_INCLUDES}
+
+CDEFINES =
+CWARNINGS =
+
+DNSLIBS =      
+ISCLIBS =      .
+
+DNSDEPLIBS =
+ISCDEPLIBS =
+
+DEPLIBS =
+
+LIBS =         @LIBS@
+
+TARGETS =      rrl@EXEEXT@
+
+FILTEROBJS =   rrl.@O@
+
+SRCS =         rrl.c
+
+@BIND9_MAKE_RULES@
+
+all: rrl@EXEEXT@
+
+rrl@EXEEXT@: ${FILTEROBJS}
+       ${LIBTOOL_MODE_LINK} ${PURIFY} ${CC} ${CFLAGS} ${LDFLAGS} -o $@ ${FILTEROBJS} ${LIBS}
+
+clean distclean::
+       rm -f ${TARGETS}
+
diff --git a/bin/tests/system/rrl/clean.sh b/bin/tests/system/rrl/clean.sh
new file mode 100644 (file)
index 0000000..59a9403
--- /dev/null
@@ -0,0 +1,21 @@
+# Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+
+
+# Clean up after rrl tests.
+
+rm -f dig.out*
+rm -f  */named.memstats */named.run */named.stats */log-* */session.key
+rm -f ns3/bl*.db */*.jnl */*.core */*.pid
diff --git a/bin/tests/system/rrl/ns1/named.conf b/bin/tests/system/rrl/ns1/named.conf
new file mode 100644 (file)
index 0000000..e0091d7
--- /dev/null
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+controls { /* empty */ };
+
+options {
+       query-source address 10.53.0.1;
+       notify-source 10.53.0.1;
+       transfer-source 10.53.0.1;
+       port 5300;
+       session-keyfile "session.key";
+       pid-file "named.pid";
+       listen-on { 10.53.0.1; };
+       listen-on-v6 { none; };
+       notify no;
+};
+
+zone "." {type master; file "root.db";};
diff --git a/bin/tests/system/rrl/ns1/root.db b/bin/tests/system/rrl/ns1/root.db
new file mode 100644 (file)
index 0000000..9b648a5
--- /dev/null
@@ -0,0 +1,31 @@
+; Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+
+$TTL   120
+@              SOA     ns. hostmaster.ns. ( 1 3600 1200 604800 60 )
+@              NS      ns.
+ns.            A       10.53.0.1
+.              A       10.53.0.1
+
+; limit responses from here
+tld2.          NS      ns.tld2.
+ns.tld2.       A       10.53.0.2
+
+; limit recursion to here
+tld3.          NS      ns.tld3.
+ns.tld3.       A       10.53.0.3
+
+; generate SERVFAIL
+tld4.          NS      ns.tld3.
diff --git a/bin/tests/system/rrl/ns2/hints b/bin/tests/system/rrl/ns2/hints
new file mode 100644 (file)
index 0000000..d5b3a45
--- /dev/null
@@ -0,0 +1,18 @@
+; Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+
+
+.      0       NS      ns1.
+ns1.   0       A       10.53.0.1
diff --git a/bin/tests/system/rrl/ns2/named.conf b/bin/tests/system/rrl/ns2/named.conf
new file mode 100644 (file)
index 0000000..9695325
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+controls { /* empty */ };
+
+options {
+       query-source address 10.53.0.2;
+       notify-source 10.53.0.2;
+       transfer-source 10.53.0.2;
+       port 5300;
+       session-keyfile "session.key";
+       pid-file "named.pid";
+       statistics-file "named.stats";
+       listen-on { 10.53.0.2; };
+       listen-on-v6 { none; };
+       notify no;
+
+       rate-limit {
+           responses-per-second 2;
+           all-per-second 70;
+           slip 3;
+           exempt-clients { 10.53.0.7; };
+
+           // small enough to force a table expansion
+           min-table-size 75;
+       };
+
+       additional-from-cache no;
+};
+
+key rndc_key {
+       secret "1234abcd8765";
+       algorithm hmac-md5;
+};
+controls {
+       inet 10.53.0.2 port 9953 allow { any; } keys { rndc_key; };
+};
+
+/*
+ * These log settings have no effect unless "-g" is removed from ../../start.pl
+ */
+logging {
+       channel debug {
+           file "log-debug";
+           print-category yes; print-severity yes; severity debug 10;
+       };
+       channel queries {
+           file "log-queries";
+           print-category yes; print-severity yes; severity info;
+       };
+       category rate-limit { debug; queries; };
+       category queries { debug; queries; };
+};
+
+zone "." { type hint; file "hints"; };
+
+zone "tld2."{ type master; file "tld2.db"; };
diff --git a/bin/tests/system/rrl/ns2/tld2.db b/bin/tests/system/rrl/ns2/tld2.db
new file mode 100644 (file)
index 0000000..20930ed
--- /dev/null
@@ -0,0 +1,47 @@
+; Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+
+
+; rate limit response from this zone
+
+$TTL   120
+@              SOA     tld2.  hostmaster.ns.tld2. ( 1 3600 1200 604800 60 )
+               NS      ns
+               NS      .
+ns             A       10.53.0.2
+
+; basic rate limiting
+a1             A       192.0.2.1
+
+; wildcards
+*.a2           A       192.0.2.2
+
+; a3 is in tld3
+
+; a4 does not exist to give NXDOMAIN
+
+; a5 for TCP requests
+a5             A       192.0.2.5
+
+; a6 for whitelisted clients
+a6             A       192.0.2.6
+
+; a7 for SERVFAIL
+
+; a8 for NODATA
+a8             A       192.0.2.8
+
+; a9 for all-per-second limit
+$GENERATE 101-180 all$.a9 A 192.0.2.8
diff --git a/bin/tests/system/rrl/ns3/hints b/bin/tests/system/rrl/ns3/hints
new file mode 100644 (file)
index 0000000..d5b3a45
--- /dev/null
@@ -0,0 +1,18 @@
+; Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+
+
+.      0       NS      ns1.
+ns1.   0       A       10.53.0.1
diff --git a/bin/tests/system/rrl/ns3/named.conf b/bin/tests/system/rrl/ns3/named.conf
new file mode 100644 (file)
index 0000000..a3c31e0
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+controls { /* empty */ };
+
+options {
+       query-source address 10.53.0.3;
+       notify-source 10.53.0.3;
+       transfer-source 10.53.0.3;
+       port 5300;
+       session-keyfile "session.key";
+       pid-file "named.pid";
+       listen-on { 10.53.0.3; };
+       listen-on-v6 { none; };
+       notify no;
+
+       // check that all of the options are parsed without limiting anything
+       rate-limit {
+           responses-per-second 200;
+           referrals-per-second 220;
+           nodata-per-second 230;
+           nxdomains-per-second 240;
+           errors-per-second 250;
+           all-per-second 700;
+           ipv4-prefix-length 24;
+           ipv6-prefix-length 64;
+           qps-scale 10;
+           window 1;
+           max-table-size 1000;
+       };
+
+};
+
+zone "." { type hint; file "hints"; };
+
+zone "tld3."{ type master; file "tld3.db"; };
diff --git a/bin/tests/system/rrl/ns3/tld3.db b/bin/tests/system/rrl/ns3/tld3.db
new file mode 100644 (file)
index 0000000..870fc2a
--- /dev/null
@@ -0,0 +1,25 @@
+; Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+;
+; Permission to use, copy, modify, and/or distribute this software for any
+; purpose with or without fee is hereby granted, provided that the above
+; copyright notice and this permission notice appear in all copies.
+;
+; THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+; REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+; AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+; INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+; LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+; OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+; PERFORMANCE OF THIS SOFTWARE.
+
+
+
+; rate limit response from this zone
+
+$TTL   120
+@              SOA     tld3.  hostmaster.ns.tld3. ( 1 3600 1200 604800 60 )
+               NS      ns
+               NS      .
+ns             A       10.53.0.3
+
+*.a3           A       192.0.3.3
diff --git a/bin/tests/system/rrl/prereq.sh b/bin/tests/system/rrl/prereq.sh
new file mode 100644 (file)
index 0000000..f23d488
--- /dev/null
@@ -0,0 +1,23 @@
+#!/bin/sh
+#
+# Copyright (C) 2010, 2012  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+if ./rrl
+then
+    :
+else
+    echo "I:This test requires --enable-rrl at compile time." >&2
+    exit 1
+fi
diff --git a/bin/tests/system/rrl/rrl.c b/bin/tests/system/rrl/rrl.c
new file mode 100644 (file)
index 0000000..6e98e8d
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <config.h>
+#include <isc/util.h>
+
+int
+main(int argc, char **argv) {
+
+       UNUSED(argc);
+       UNUSED(argv);
+
+#ifdef USE_RRL
+       return (0);
+#else
+       return (1);
+#endif
+}
diff --git a/bin/tests/system/rrl/setup.sh b/bin/tests/system/rrl/setup.sh
new file mode 100644 (file)
index 0000000..8eb1a70
--- /dev/null
@@ -0,0 +1,21 @@
+#!/bin/sh
+#
+# Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+. ./clean.sh
+
diff --git a/bin/tests/system/rrl/tests.sh b/bin/tests/system/rrl/tests.sh
new file mode 100644 (file)
index 0000000..6d77480
--- /dev/null
@@ -0,0 +1,258 @@
+# Copyright (C) 2012-2013  Internet Systems Consortium, Inc. ("ISC")
+#
+# Permission to use, copy, modify, and/or distribute this software for any
+# purpose with or without fee is hereby granted, provided that the above
+# copyright notice and this permission notice appear in all copies.
+#
+# THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+# AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+# PERFORMANCE OF THIS SOFTWARE.
+
+
+# test response rate limiting
+
+SYSTEMTESTTOP=..
+. $SYSTEMTESTTOP/conf.sh
+
+#set -x
+#set -o noclobber
+
+ns1=10.53.0.1                      # root, defining the others
+ns2=10.53.0.2                      # test server
+ns3=10.53.0.3                      # secondary test server
+ns7=10.53.0.7                      # whitelisted client
+
+USAGE="$0: [-x]"
+while getopts "x" c; do
+    case $c in
+       x) set -x;;
+       *) echo "$USAGE" 1>&2; exit 1;;
+    esac
+done
+shift `expr $OPTIND - 1 || true`
+if test "$#" -ne 0; then
+    echo "$USAGE" 1>&2
+    exit 1
+fi
+# really quit on control-C
+trap 'exit 1' 1 2 15
+
+
+ret=0
+setret () {
+    ret=1
+    echo "$*"
+}
+
+
+# Wait until soon after the start of a second to make results consistent.
+#   The start of a second credits a rate limit.
+#   This would be far easier in C or by assuming a modern version of perl.
+sec_start () {
+    START=`date`
+    while true; do
+       NOW=`date`
+       if test "$START" != "$NOW"; then
+           return
+       fi
+       $PERL -e 'select(undef, undef, undef, 0.05)' || true
+    done
+}
+
+
+# turn off ${HOME}/.digrc
+HOME=/dev/null; export HOME
+
+#   $1=result name  $2=domain name  $3=dig options
+digcmd () {
+    OFILE=$1; shift
+    DIG_DOM=$1; shift
+    ARGS="+nosearch +time=1 +tries=1 +ignore -p 5300 $* $DIG_DOM @$ns2"
+    #echo I:dig $ARGS 1>&2
+    START=`date +%y%m%d%H%M.%S`
+    RESULT=`$DIG $ARGS 2>&1 | tee $OFILE=TEMP                          \
+           | sed -n -e '/^;; AUTHORITY/,/^$/d'                         \
+               -e '/^;; ADDITIONAL/,/^$/d'                             \
+               -e  's/^[^;].*  \([^     ]\{1,\}\)$/\1/p'               \
+               -e 's/;; flags.* tc .*/TC/p'                            \
+               -e 's/;; .* status: NXDOMAIN.*/NXDOMAIN/p'              \
+               -e 's/;; .* status: SERVFAIL.*/SERVFAIL/p'              \
+               -e 's/;; connection timed out.*/drop/p'                 \
+               -e 's/;; communications error to.*/drop/p'              \
+           | tr -d '\n'`
+    mv "$OFILE=TEMP" "$OFILE=$RESULT"
+    touch -t $START "$OFILE=$RESULT"
+}
+
+
+#   $1=number of tests  $2=target domain  $3=dig options
+CNT=1
+burst () {
+    BURST_LIMIT=$1; shift
+    BURST_DOM_BASE="$1"; shift
+    while test "$BURST_LIMIT" -ge 1; do
+       if test $CNT -lt 10; then
+           CNT="00$CNT"
+       else
+           if test $CNT -lt 100; then
+               CNT="0$CNT"
+           fi
+       fi
+       eval BURST_DOM="$BURST_DOM_BASE"
+       FILE="dig.out-$BURST_DOM-$CNT"
+       digcmd $FILE $BURST_DOM $* &
+       CNT=`expr $CNT + 1`
+       BURST_LIMIT=`expr "$BURST_LIMIT" - 1`
+    done
+}
+
+
+#   $1=domain  $2=IP address  $3=# of IP addresses  $4=TC  $5=drop
+#      $6=NXDOMAIN  $7=SERVFAIL or other errors
+ck_result() {
+    BAD=
+    wait
+    ADDRS=`ls dig.out-$1-*=$2          2>/dev/null     | wc -l | tr -d ' '`
+    TC=`ls dig.out-$1-*=TC             2>/dev/null     | wc -l | tr -d ' '`
+    DROP=`ls dig.out-$1-*=drop         2>/dev/null     | wc -l | tr -d ' '`
+    NXDOMAIN=`ls dig.out-$1-*=NXDOMAIN 2>/dev/null     | wc -l | tr -d ' '`
+    SERVFAIL=`ls dig.out-$1-*=SERVFAIL 2>/dev/null     | wc -l | tr -d ' '`
+    if test $ADDRS -ne "$3"; then
+       setret "I:$ADDRS instead of $3 '$2' responses for $1"
+       BAD=yes
+    fi
+    if test $TC -ne "$4"; then
+       setret "I:$TC instead of $4 truncation responses for $1"
+       BAD=yes
+    fi
+    if test $DROP -ne "$5"; then
+       setret "I:$DROP instead of $5 dropped responses for $1"
+       BAD=yes
+    fi
+    if test $NXDOMAIN -ne "$6"; then
+       setret "I:$NXDOMAIN instead of $6 NXDOMAIN responses for $1"
+       BAD=yes
+    fi
+    if test $SERVFAIL -ne "$7"; then
+       setret "I:$SERVFAIL instead of $7 error responses for $1"
+       BAD=yes
+    fi
+    if test -z "$BAD"; then
+       rm -f dig.out-$1-*
+    fi
+}
+
+
+ckstats () {
+    LABEL="$1"; shift
+    TYPE="$1"; shift
+    EXPECTED="$1"; shift
+    CNT=`sed -n -e "s/[         ]*\([0-9]*\).responses $TYPE for rate limits.*/\1/p"  \
+           ns2/named.stats | tail -1`
+    CNT=`expr 0$CNT + 0`
+    if test "$CNT" -ne $EXPECTED; then
+       setret "I:wrong $LABEL $TYPE statistics of $CNT instead of $EXPECTED"
+    fi
+}
+
+
+#########
+sec_start
+
+# Tests of referrals to "." must be done before the hints are loaded
+#   or with "additional-from-cache no"
+burst 5 a1.tld3 +norec
+# basic rate limiting
+burst 3 a1.tld2
+# 1 second delay allows an additional response.
+sleep 1
+burst 21 a1.tld2
+# Request 30 different qnames to try a wildcard.
+burst 30 'x$CNT.a2.tld2'
+# These should be counted and limited but are not.  See RT33138.
+burst 10 'y.x$CNT.a2.tld2'
+
+#                                      IP      TC      drop  NXDOMAIN SERVFAIL
+# referrals to "."
+ck_result   a1.tld3    ''              2       1       2       0       0
+# check 24 results including 1 second delay that allows an additional response
+ck_result   a1.tld2    192.0.2.1       3       7       14      0       0
+
+# Check the wild card answers.
+# The parent name of the 30 requests is counted.
+ck_result 'x*.a2.tld2' 192.0.2.2       2       10      18      0       0
+
+# These should be limited but are not.  See RT33138.
+ck_result 'y.x*.a2.tld2' 192.0.2.2     10      0       0       0       0
+
+#########
+sec_start
+
+burst 1 'y$CNT.a3.tld3'; wait; burst 20 'y$CNT.a3.tld3'
+burst 20 'z$CNT.a4.tld2'
+
+# Recursion.
+#   The first answer is counted separately because it is counted against
+#   the rate limit on recursing to the server for a3.tld3.  The remaining 20
+#   are counted as local responses from the cache.
+ck_result 'y*.a3.tld3' 192.0.3.3       3       6       12      0       0
+
+# NXDOMAIN responses are also limited based on the parent name.
+ck_result 'z*.a4.tld2' x               0       6       12      2       0
+
+$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats
+ckstats first dropped 58
+ckstats first truncated 30
+
+
+#########
+sec_start
+
+burst 20 a5.tld2 +tcp
+burst 20 a6.tld2 -b $ns7
+burst 20 a7.tld4
+burst 2 a8.tld2 AAAA
+burst 2 a8.tld2 TXT
+burst 2 a8.tld2 SPF
+
+# TCP responses are not rate limited
+ck_result a5.tld2      192.0.2.5       20      0       0       0       0
+
+# whitelisted client is not rate limited
+ck_result a6.tld2      192.0.2.6       20      0       0       0       0
+
+# Errors such as SERVFAIL are rate limited.  The numbers are confusing, because
+#   other rate limiting can be triggered before the SERVFAIL limit is reached.
+ck_result a7.tld4      192.0.2.1       0       6       12      0       2
+
+# NODATA responses are counted as the same regardless of qtype.
+ck_result a8.tld2      ''              2       2       2       0       0
+
+$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats
+ckstats second dropped 72
+ckstats second truncated 38
+
+
+#########
+sec_start
+
+# all-per-second
+#   The qnames are all unique but the client IP address is constant.
+CNT=101
+burst 80 'all$CNT.a9.tld2'
+
+ck_result 'a*.a9.tld2' 192.0.2.8       70      0       10      0       0
+
+$RNDC -c $SYSTEMTESTTOP/common/rndc.conf -p 9953 -s $ns2 stats
+ckstats final dropped 82
+ckstats final truncated 38
+
+
+echo "I:exit status: $ret"
+# exit $ret
+[ $ret -ne 0 ] && echo "I:test failure overridden"
+exit 0
index a6ddcb1ed55df1906a576f2b865e8c10e7258849..85a43815d1934699ba3d49561c5ad51a184a9f72 100644 (file)
@@ -428,6 +428,9 @@ int sigwait(const unsigned int *set, int *sig);
    non-blocking. */
 #undef USE_FIONBIO_IOCTL
 
+/* Enable DNS Response Rate Limiting */
+#undef USE_RRL
+
 /* define if idnkit support is to be included. */
 #undef WITH_IDN
 
index 726ee6c24b8764ca6019fcd685514bd09f772977..8c1e044b079bed2cf20fb11f4defb22770760caf 100755 (executable)
--- a/configure
+++ b/configure
@@ -1331,6 +1331,8 @@ ISC_PLATFORM_NEEDSYSSELECTH
 ISC_PLATFORM_HAVEDEVPOLL
 ISC_PLATFORM_HAVEEPOLL
 ISC_PLATFORM_HAVEKQUEUE
+RRLLINKSRCS
+RRLLINKOBJS
 ISC_PLATFORM_HAVELIFCONF
 ISC_PLATFORM_NORETURN_POST
 ISC_PLATFORM_NORETURN_PRE
@@ -1452,6 +1454,7 @@ enable_libbind
 enable_developer
 with_python
 enable_newstats
+enable_rrl
 enable_kqueue
 enable_epoll
 enable_devpoll
@@ -2129,7 +2132,8 @@ Optional Features:
   --disable-libtool-lock  avoid locking (might break parallel builds)
   --enable-libbind       deprecated
   --enable-developer      enable developer build settings
-  --enable-newstats         use the new statistics
+  --enable-newstats       use the new statistics
+  --enable-rrl            use DNS Response Rate Limiting
   --enable-kqueue         use BSD kqueue when available [default=yes]
   --enable-epoll          use Linux epoll when available [default=auto]
   --enable-devpoll        use /dev/poll when available [default=yes]
@@ -11865,6 +11869,7 @@ yes)
        test "${enable_fixed_rrset+set}" = set || enable_fixed_rrset=yes
        test "${with_atf+set}" = set || with_atf=yes
        test "${enable_filter_aaaa+set}" = set || enable_filter_aaaa=yes
+       test "${enable_rrl+set}" = set || enable_rrl=yes
        test "${with_dlz_filesystem+set}" = set || with_dlz_filesystem=yes
        case "$host" in
        *-darwin*)
@@ -13544,6 +13549,30 @@ $as_echo "#define NEWSTATS 1" >>confdefs.h
        ;;
 esac
 
+#
+# check if we want DNS RRL
+#
+# Check whether --enable-rrl was given.
+if test "${enable_rrl+set}" = set; then :
+  enableval=$enable_rrl;
+fi
+
+case "$enable_rrl" in
+yes)
+
+$as_echo "#define USE_RRL 1" >>confdefs.h
+
+        RRLLINKOBJS='${RRLOBJS}'
+        RRLLINKSRCS='${RRLSRCS}'
+       ;;
+*)
+        RRLLINKOBJS=""
+        RRLLINKSRCS=""
+       ;;
+esac
+
+
+
 #
 # check if we have kqueue
 #
@@ -20526,7 +20555,7 @@ ac_config_commands="$ac_config_commands chmod"
 # elsewhere if there's a good reason for doing so.
 #
 
-ac_config_files="$ac_config_files make/Makefile make/mkdep Makefile bin/Makefile bin/check/Makefile bin/confgen/Makefile bin/confgen/unix/Makefile bin/dig/Makefile bin/dnssec/Makefile bin/named/Makefile bin/named/unix/Makefile bin/nsupdate/Makefile bin/pkcs11/Makefile bin/python/Makefile bin/python/dnssec-checkds.py bin/python/dnssec-coverage.py bin/rndc/Makefile bin/tests/Makefile bin/tests/atomic/Makefile bin/tests/db/Makefile bin/tests/dst/Makefile bin/tests/dst/Kdh.+002+18602.key bin/tests/dst/Kdh.+002+18602.private bin/tests/dst/Kdh.+002+48957.key bin/tests/dst/Kdh.+002+48957.private bin/tests/dst/Ktest.+001+00002.key bin/tests/dst/Ktest.+001+54622.key bin/tests/dst/Ktest.+001+54622.private bin/tests/dst/Ktest.+003+23616.key bin/tests/dst/Ktest.+003+23616.private bin/tests/dst/Ktest.+003+49667.key bin/tests/dst/dst_2_data bin/tests/dst/t2_data_1 bin/tests/dst/t2_data_2 bin/tests/dst/t2_dsasig bin/tests/dst/t2_rsasig bin/tests/hashes/Makefile bin/tests/headerdep_test.sh bin/tests/master/Makefile bin/tests/mem/Makefile bin/tests/names/Makefile bin/tests/net/Makefile bin/tests/rbt/Makefile bin/tests/resolver/Makefile bin/tests/sockaddr/Makefile bin/tests/system/Makefile bin/tests/system/conf.sh bin/tests/system/dlz/prereq.sh bin/tests/system/dlzexternal/Makefile bin/tests/system/dlzexternal/ns1/named.conf bin/tests/system/ecdsa/prereq.sh bin/tests/system/filter-aaaa/Makefile bin/tests/system/gost/prereq.sh bin/tests/system/lwresd/Makefile bin/tests/system/rpz/Makefile bin/tests/system/rsabigexponent/Makefile bin/tests/system/tkey/Makefile bin/tests/system/tsiggss/Makefile bin/tests/tasks/Makefile bin/tests/timers/Makefile bin/tests/virtual-time/Makefile bin/tests/virtual-time/conf.sh bin/tools/Makefile contrib/check-secure-delegation.pl contrib/zone-edit.sh doc/Makefile doc/arm/Makefile doc/doxygen/Doxyfile doc/doxygen/Makefile doc/doxygen/doxygen-input-filter doc/misc/Makefile doc/xsl/Makefile doc/xsl/isc-docbook-chunk.xsl doc/xsl/isc-docbook-html.xsl doc/xsl/isc-docbook-latex.xsl doc/xsl/isc-manpage.xsl isc-config.sh lib/Makefile lib/bind9/Makefile lib/bind9/include/Makefile lib/bind9/include/bind9/Makefile lib/dns/Makefile lib/dns/include/Makefile lib/dns/include/dns/Makefile lib/dns/include/dst/Makefile lib/dns/tests/Makefile lib/export/Makefile lib/export/dns/Makefile lib/export/dns/include/Makefile lib/export/dns/include/dns/Makefile lib/export/dns/include/dst/Makefile lib/export/irs/Makefile lib/export/irs/include/Makefile lib/export/irs/include/irs/Makefile lib/export/isc/$thread_dir/Makefile lib/export/isc/$thread_dir/include/Makefile lib/export/isc/$thread_dir/include/isc/Makefile lib/export/isc/Makefile lib/export/isc/include/Makefile lib/export/isc/include/isc/Makefile lib/export/isc/nls/Makefile lib/export/isc/unix/Makefile lib/export/isc/unix/include/Makefile lib/export/isc/unix/include/isc/Makefile lib/export/isccfg/Makefile lib/export/isccfg/include/Makefile lib/export/isccfg/include/isccfg/Makefile lib/export/samples/Makefile lib/export/samples/Makefile-postinstall lib/irs/Makefile lib/irs/include/Makefile lib/irs/include/irs/Makefile lib/irs/include/irs/netdb.h lib/irs/include/irs/platform.h lib/isc/$arch/Makefile lib/isc/$arch/include/Makefile lib/isc/$arch/include/isc/Makefile lib/isc/$thread_dir/Makefile lib/isc/$thread_dir/include/Makefile lib/isc/$thread_dir/include/isc/Makefile lib/isc/Makefile lib/isc/include/Makefile lib/isc/include/isc/Makefile lib/isc/include/isc/platform.h lib/isc/tests/Makefile lib/isc/nls/Makefile lib/isc/unix/Makefile lib/isc/unix/include/Makefile lib/isc/unix/include/isc/Makefile lib/isccc/Makefile lib/isccc/include/Makefile lib/isccc/include/isccc/Makefile lib/isccfg/Makefile lib/isccfg/include/Makefile lib/isccfg/include/isccfg/Makefile lib/lwres/Makefile lib/lwres/include/Makefile lib/lwres/include/lwres/Makefile lib/lwres/include/lwres/netdb.h lib/lwres/include/lwres/platform.h lib/lwres/man/Makefile lib/lwres/unix/Makefile lib/lwres/unix/include/Makefile lib/lwres/unix/include/lwres/Makefile lib/tests/Makefile lib/tests/include/Makefile lib/tests/include/tests/Makefile unit/Makefile unit/unittest.sh"
+ac_config_files="$ac_config_files make/Makefile make/mkdep Makefile bin/Makefile bin/check/Makefile bin/confgen/Makefile bin/confgen/unix/Makefile bin/dig/Makefile bin/dnssec/Makefile bin/named/Makefile bin/named/unix/Makefile bin/nsupdate/Makefile bin/pkcs11/Makefile bin/python/Makefile bin/python/dnssec-checkds.py bin/python/dnssec-coverage.py bin/rndc/Makefile bin/tests/Makefile bin/tests/atomic/Makefile bin/tests/db/Makefile bin/tests/dst/Makefile bin/tests/dst/Kdh.+002+18602.key bin/tests/dst/Kdh.+002+18602.private bin/tests/dst/Kdh.+002+48957.key bin/tests/dst/Kdh.+002+48957.private bin/tests/dst/Ktest.+001+00002.key bin/tests/dst/Ktest.+001+54622.key bin/tests/dst/Ktest.+001+54622.private bin/tests/dst/Ktest.+003+23616.key bin/tests/dst/Ktest.+003+23616.private bin/tests/dst/Ktest.+003+49667.key bin/tests/dst/dst_2_data bin/tests/dst/t2_data_1 bin/tests/dst/t2_data_2 bin/tests/dst/t2_dsasig bin/tests/dst/t2_rsasig bin/tests/hashes/Makefile bin/tests/headerdep_test.sh bin/tests/master/Makefile bin/tests/mem/Makefile bin/tests/names/Makefile bin/tests/net/Makefile bin/tests/rbt/Makefile bin/tests/resolver/Makefile bin/tests/sockaddr/Makefile bin/tests/system/Makefile bin/tests/system/conf.sh bin/tests/system/dlz/prereq.sh bin/tests/system/dlzexternal/Makefile bin/tests/system/dlzexternal/ns1/named.conf bin/tests/system/ecdsa/prereq.sh bin/tests/system/filter-aaaa/Makefile bin/tests/system/gost/prereq.sh bin/tests/system/lwresd/Makefile bin/tests/system/rpz/Makefile bin/tests/system/rrl/Makefile bin/tests/system/rsabigexponent/Makefile bin/tests/system/tkey/Makefile bin/tests/system/tsiggss/Makefile bin/tests/tasks/Makefile bin/tests/timers/Makefile bin/tests/virtual-time/Makefile bin/tests/virtual-time/conf.sh bin/tools/Makefile contrib/check-secure-delegation.pl contrib/zone-edit.sh doc/Makefile doc/arm/Makefile doc/doxygen/Doxyfile doc/doxygen/Makefile doc/doxygen/doxygen-input-filter doc/misc/Makefile doc/xsl/Makefile doc/xsl/isc-docbook-chunk.xsl doc/xsl/isc-docbook-html.xsl doc/xsl/isc-docbook-latex.xsl doc/xsl/isc-manpage.xsl isc-config.sh lib/Makefile lib/bind9/Makefile lib/bind9/include/Makefile lib/bind9/include/bind9/Makefile lib/dns/Makefile lib/dns/include/Makefile lib/dns/include/dns/Makefile lib/dns/include/dst/Makefile lib/dns/tests/Makefile lib/export/Makefile lib/export/dns/Makefile lib/export/dns/include/Makefile lib/export/dns/include/dns/Makefile lib/export/dns/include/dst/Makefile lib/export/irs/Makefile lib/export/irs/include/Makefile lib/export/irs/include/irs/Makefile lib/export/isc/$thread_dir/Makefile lib/export/isc/$thread_dir/include/Makefile lib/export/isc/$thread_dir/include/isc/Makefile lib/export/isc/Makefile lib/export/isc/include/Makefile lib/export/isc/include/isc/Makefile lib/export/isc/nls/Makefile lib/export/isc/unix/Makefile lib/export/isc/unix/include/Makefile lib/export/isc/unix/include/isc/Makefile lib/export/isccfg/Makefile lib/export/isccfg/include/Makefile lib/export/isccfg/include/isccfg/Makefile lib/export/samples/Makefile lib/export/samples/Makefile-postinstall lib/irs/Makefile lib/irs/include/Makefile lib/irs/include/irs/Makefile lib/irs/include/irs/netdb.h lib/irs/include/irs/platform.h lib/isc/$arch/Makefile lib/isc/$arch/include/Makefile lib/isc/$arch/include/isc/Makefile lib/isc/$thread_dir/Makefile lib/isc/$thread_dir/include/Makefile lib/isc/$thread_dir/include/isc/Makefile lib/isc/Makefile lib/isc/include/Makefile lib/isc/include/isc/Makefile lib/isc/include/isc/platform.h lib/isc/tests/Makefile lib/isc/nls/Makefile lib/isc/unix/Makefile lib/isc/unix/include/Makefile lib/isc/unix/include/isc/Makefile lib/isccc/Makefile lib/isccc/include/Makefile lib/isccc/include/isccc/Makefile lib/isccfg/Makefile lib/isccfg/include/Makefile lib/isccfg/include/isccfg/Makefile lib/lwres/Makefile lib/lwres/include/Makefile lib/lwres/include/lwres/Makefile lib/lwres/include/lwres/netdb.h lib/lwres/include/lwres/platform.h lib/lwres/man/Makefile lib/lwres/unix/Makefile lib/lwres/unix/include/Makefile lib/lwres/unix/include/lwres/Makefile lib/tests/Makefile lib/tests/include/Makefile lib/tests/include/tests/Makefile unit/Makefile unit/unittest.sh"
 
 
 #
@@ -21574,6 +21603,7 @@ do
     "bin/tests/system/gost/prereq.sh") CONFIG_FILES="$CONFIG_FILES bin/tests/system/gost/prereq.sh" ;;
     "bin/tests/system/lwresd/Makefile") CONFIG_FILES="$CONFIG_FILES bin/tests/system/lwresd/Makefile" ;;
     "bin/tests/system/rpz/Makefile") CONFIG_FILES="$CONFIG_FILES bin/tests/system/rpz/Makefile" ;;
+    "bin/tests/system/rrl/Makefile") CONFIG_FILES="$CONFIG_FILES bin/tests/system/rrl/Makefile" ;;
     "bin/tests/system/rsabigexponent/Makefile") CONFIG_FILES="$CONFIG_FILES bin/tests/system/rsabigexponent/Makefile" ;;
     "bin/tests/system/tkey/Makefile") CONFIG_FILES="$CONFIG_FILES bin/tests/system/tkey/Makefile" ;;
     "bin/tests/system/tsiggss/Makefile") CONFIG_FILES="$CONFIG_FILES bin/tests/system/tsiggss/Makefile" ;;
index 8e543d85fdda25afa8998280dbbbb2afc74c2c3c..2bdf91be5ca2aea8e866dfda9d943751908b7fd0 100644 (file)
@@ -70,6 +70,7 @@ yes)
        test "${enable_fixed_rrset+set}" = set || enable_fixed_rrset=yes
        test "${with_atf+set}" = set || with_atf=yes
        test "${enable_filter_aaaa+set}" = set || enable_filter_aaaa=yes
+       test "${enable_rrl+set}" = set || enable_rrl=yes
        test "${with_dlz_filesystem+set}" = set || with_dlz_filesystem=yes
        case "$host" in
        *-darwin*)
@@ -469,7 +470,7 @@ AC_SUBST(ISC_PLATFORM_HAVELIFCONF)
 # check if we want the new statistics
 #
 AC_ARG_ENABLE(newstats,
-       [  --enable-newstats         use the new statistics])
+       [  --enable-newstats       use the new statistics])
 case "$enable_newstats" in
 yes)
         AC_DEFINE(NEWSTATS, 1, [Use the new XML schema for statistics])
@@ -478,6 +479,25 @@ yes)
        ;;
 esac
 
+#
+# check if we want DNS RRL
+#
+AC_ARG_ENABLE(rrl,
+       [  --enable-rrl            use DNS Response Rate Limiting])
+case "$enable_rrl" in
+yes)
+        AC_DEFINE(USE_RRL, 1, [Enable DNS Response Rate Limiting])
+        RRLLINKOBJS='${RRLOBJS}'
+        RRLLINKSRCS='${RRLSRCS}'
+       ;;
+*)
+        RRLLINKOBJS=""
+        RRLLINKSRCS=""
+       ;;
+esac
+AC_SUBST(RRLLINKOBJS)
+AC_SUBST(RRLLINKSRCS)
+
 #
 # check if we have kqueue
 #
@@ -3690,6 +3710,7 @@ AC_CONFIG_FILES([
        bin/tests/system/gost/prereq.sh
        bin/tests/system/lwresd/Makefile
        bin/tests/system/rpz/Makefile
+       bin/tests/system/rrl/Makefile
        bin/tests/system/rsabigexponent/Makefile
        bin/tests/system/tkey/Makefile
        bin/tests/system/tsiggss/Makefile
index 47e0067c67a7a33e27d97a09a7b8bdb31fdd498f..13173f3f50baf0b9aece771d2678e13fa7b7b836 100644 (file)
@@ -4818,6 +4818,37 @@ category notify { null; };
                    </para>
                  </entry>
                </row>
+                <row rowsep="0">
+                  <entry colname="1">
+                    <para><command>rate-limit</command></para>
+                  </entry>
+                 <entry colname="2">
+                   <para>
+                      (Only available when <acronym>BIND</acronym> 9 is
+                      configured with the <userinput>--enable-rrl</userinput>
+                      option at compile time.)
+                    </para>
+                   <para>
+                     The start, periodic, and final notices of the
+                     rate limiting of a stream of responses are logged at
+                     <command>info</command> severity in this category.
+                     These messages include a hash value of the domain name
+                     of the response and the name itself,
+                     except when there is insufficient memory to record
+                     the name for the final notice
+                     The final notice is normally delayed until about one
+                     minute after rate limit stops.
+                     A lack of memory can hurry the final notice,
+                     in which case it starts with an asterisk (*).
+                     Various internal events are logged at debug 1 level
+                     and higher.
+                   </para>
+                   <para>
+                     Rate limiting of individual requests
+                     is logged in the <command>query-errors</command> category.
+                   </para>
+                 </entry>
+               </row>
              </tbody>
            </tgroup>
          </informaltable>
@@ -5318,7 +5349,7 @@ badresp:1,adberr:0,findfail:0,valfail:0]
     <optional> match-mapped-addresses <replaceable>yes_or_no</replaceable>; </optional>
     <optional> filter-aaaa-on-v4 ( <replaceable>yes_or_no</replaceable> | <replaceable>break-dnssec</replaceable> ); </optional>
     <optional> filter-aaaa { <replaceable>address_match_list</replaceable> }; </optional>
-    <optional> dns64 <replaceable>IPv6-prefix</replaceable> {
+    <optional> dns64 <replaceable>ipv6-prefix</replaceable> {
        <optional> clients { <replaceable>address_match_list</replaceable> }; </optional>
        <optional> mapped { <replaceable>address_match_list</replaceable> }; </optional>
         <optional> exclude { <replaceable>address_match_list</replaceable> }; </optional>
@@ -5351,6 +5382,23 @@ badresp:1,adberr:0,findfail:0,valfail:0]
     <optional> resolver-query-timeout <replaceable>number</replaceable> ; </optional>
     <optional> deny-answer-addresses { <replaceable>address_match_list</replaceable> } <optional> except-from { <replaceable>namelist</replaceable> } </optional>;</optional>
     <optional> deny-answer-aliases { <replaceable>namelist</replaceable> } <optional> except-from { <replaceable>namelist</replaceable> } </optional>;</optional>
+    <optional> rate-limit {
+       <optional> responses-per-second <replaceable>number</replaceable> ; </optional>
+       <optional> referrals-per-second <replaceable>number</replaceable> ; </optional>
+       <optional> nodata-per-second <replaceable>number</replaceable> ; </optional>
+       <optional> nxdomains-per-second <replaceable>number</replaceable> ; </optional>
+       <optional> errors-per-second <replaceable>number</replaceable> ; </optional>
+       <optional> all-per-second <replaceable>number</replaceable> ; </optional>
+       <optional> window <replaceable>number</replaceable> ; </optional>
+       <optional> log-only <replaceable>yes_or_no</replaceable> ; </optional>
+       <optional> qps-scale <replaceable>number</replaceable> ; </optional>
+       <optional> ipv4-prefix-length <replaceable>number</replaceable> ; </optional>
+       <optional> ipv6-prefix-length <replaceable>number</replaceable> ; </optional>
+       <optional> slip <replaceable>number</replaceable> ; </optional>
+       <optional> exempt-clients  { <replaceable>address_match_list</replaceable> } ; </optional>
+       <optional> max-table-size <replaceable>number</replaceable> ; </optional>
+       <optional> min-table-size <replaceable>number</replaceable> ; </optional>
+      } ; </optional>
     <optional> response-policy { <replaceable>zone_name</replaceable>
        <optional> policy given | disabled | passthru | nxdomain | nodata | cname <replaceable>domain</replaceable> </optional>
        <optional> recursive-only <replaceable>yes_or_no</replaceable> </optional> <optional> max-policy-ttl <replaceable>number</replaceable> </optional> ;
@@ -9898,6 +9946,228 @@ ns.domain.com.rpz-nsdname   CNAME   .
             <command>RPZRewrites</command> statistics.
           </para>
         </sect3>
+
+       <sect3>
+         <title>Response Rate Limiting</title>
+         <para>
+            This feature is only available when <acronym>BIND</acronym> 9
+            is compiled with the <userinput>--enable-rrl</userinput>
+            option on the "configure" command line.
+          </para>
+         <para>
+           Excessive almost identical UDP <emphasis>responses</emphasis>
+           can be controlled by configuring a
+           <command>rate-limit</command> clause in an
+           <command>options</command> or <command>view</command> statement.
+           This mechanism keeps authoritative BIND 9 from being used
+           in amplifying reflection denial of service (DoS) attacks.
+           Short truncated (TC=1) responses can be sent to provide
+           rate-limited responses to legitimate clients within
+           a range of forged, attacked IP addresses.
+           Legitimate clients react to dropped or truncated response
+           by retrying with UDP or with TCP respectively.
+         </para>
+
+         <para>
+           This mechanism is intended for authoritative DNS servers.
+           It can be used on recursive servers but can slow
+           applications such as SMTP servers (mail receivers) and
+           HTTP clients (web browsers) that repeatedly request the
+           same domains.
+           When possible, closing "open" recursive servers is better.
+         </para>
+
+         <para>
+           Response rate limiting uses a "credit" or "token bucket" scheme.
+           Each combination of identical response and client
+           has a conceptual account that earns a specified number
+           of credits every second.
+           A prospective response debits its account by one.
+           Responses are dropped or truncated
+           while the account is negative.
+            Responses are tracked within a rolling window of time
+            which defaults to 15 seconds, but can be configured with
+            the <command>window</command> option to any value from
+            1 to 3600 seconds (1 hour).
+           The account cannot become more positive than
+           the per-second limit
+           or more negative than <command>window</command>
+           times the per-second limit.
+            When the specified number of credits for a class of
+            responses is set to 0, those responses are not rate limited.
+         </para>
+
+         <para>
+           The notions of "identical response" and "DNS client"
+           for rate limiting are not simplistic.
+           All responses to an address block are counted as if to a
+           single client.
+           The prefix lengths of addresses blocks are
+           specified with <command>ipv4-prefix-length</command> (default 24)
+           and <command>ipv6-prefix-length</command> (default 56).
+         </para>
+
+         <para>
+           All non-empty responses for a valid domain name (qname)
+           and record type (qtype) are identical and have a limit specified
+           with <command>responses-per-second</command>
+           (default 0 or no limit).
+           All empty (NODATA) responses for a valid domain,
+           regardless of query type, are identical.
+           Responses in the NODATA class are limited by
+           <command>nodata-per-second</command>
+           (default <command>responses-per-second</command>).
+           Requests for any and all undefined subdomains of a given
+            valid domain result in NXDOMAIN errors, and are identical
+            regardless of query type.
+           They are limited by <command>nxdomain-per-second</command>
+           (default <command>responses-per-second</command>).
+           This controls some attacks using random names, but
+           can be relaxed or turned off (set to 0)
+           on servers that expect many legitimate
+           NXDOMAIN responses, such as from anti-spam blacklists.
+           Referrals or delegations to the server of a given
+           domain are identical and are limited by
+           <command>referrals-per-second</command>
+           (default <command>responses-per-second</command>).
+         </para>
+
+         <para>
+           Responses generated from local wildcards are counted and limited
+           as if they were for the parent domain name.
+           This controls flooding using random.wild.example.com.
+         </para>
+
+         <para>
+            All requests that result in DNS errors other
+           than NXDOMAIN, such as SERVFAIL and FORMERR, are identical
+            regardless of requested name (qname) or record type (qtype).
+           This controls attacks using invalid requests or distant,
+           broken authoritative servers.
+           By default the limit on errors is the same as the
+           <command>responses-per-second</command> value,
+           but it can be set separately with
+           <command>errors-per-second</command>.
+         </para>
+
+         <para>
+           Many attacks using DNS involve UDP requests with forged source
+           addresses.
+           Rate limiting prevents the use of BIND 9 to flood a network
+           with responses to requests with forged source addresses,
+           but could let a third party block responses to legitimate requests.
+           There is a mechanism that can answer some legitimate
+           requests from a client whose address is being forged in a flood.
+           Setting <command>slip</command> to 2 (its default) causes every
+           other UDP request to be answered with a small truncated (TC=1)
+           response.
+           The small size and reduced frequency, and so lack of
+           amplification, of "slipped" responses make them unattractive
+           for reflection DoS attacks.
+           <command>slip</command> must be between 0 and 10.
+           A value of 0 does not "slip";
+           no truncated responses are sent due to rate limiting.
+           Some error responses including REFUSED and SERVFAIL
+           cannot be replaced with truncated responses and are instead
+           leaked at the <command>slip</command> rate.
+         </para>
+
+         <para>
+           When the approximate query per second rate exceeds
+           the <command>qps-scale</command> value,
+           then the <command>responses-per-second</command>,
+           <command>errors-per-second</command>,
+           <command>nxdomains-per-second</command> and
+           <command>all-per-second</command> values are reduced by the
+           ratio of the current rate to the <command>qps-scale</command> value.
+           This feature can tighten defenses during attacks.
+           For example, with
+           <command>qps-scale 250; responses-per-second 20;</command> and
+           a total query rate of 1000 queries/second for all queries from
+           all DNS clients including via TCP,
+           then the effective responses/second limit changes to
+           (250/1000)*20 or 5.
+           Responses sent via TCP are not limited
+           but are counted to compute the query per second rate.
+         </para>
+
+         <para>
+           Communities of DNS clients can be given their own parameters or no
+           rate limiting by putting
+           <command>rate-limit</command> statements in <command>view</command>
+           statements instead of the global <command>option</command>
+           statement.
+           A <command>rate-limit</command> statement in a view replaces,
+           rather than supplementing, a <command>rate-limit</command>
+           statement among the main options.
+           DNS clients within a view can be exempted from rate limits
+           with the <command>exempt-clients</command> clause.
+         </para>
+
+         <para>
+           UDP responses of all kinds can be limited with the
+           <command>all-per-second</command> phrase.
+           This rate limiting is unlike the rate limiting provided by
+           <command>responses-per-second</command>,
+           <command>errors-per-second</command>, and
+           <command>nxdomains-per-second</command> on a DNS server
+           which are often invisible to the victim of a DNS reflection attack.
+           Unless the forged requests of the attack are the same as the
+           legitimate requests of the victim, the victim's requests are
+           not affected.
+           Responses affected by an <command>all-per-second</command> limit
+           are always dropped; the <command>slip</command> value has no
+           effect.
+           An <command>all-per-second</command> limit should be
+           at least 4 times as large as the other limits,
+           because single DNS clients often send bursts of legitimate
+           requests.
+           For example, the receipt of a single mail message can prompt
+           requests from an SMTP server for NS, PTR, A, and AAAA records
+           as the incoming SMTP/TCP/IP connection is considered.
+           The SMTP server can need additional NS, A, AAAA, MX, TXT, and SPF
+           records as it considers the STMP <command>Mail From</command>
+           command.
+           Web browsers often repeatedly resolve the same names that
+           are repeated in HTML &lt;IMG&gt; tags in a page.
+           <command>All-per-second</command> is similar to the
+           rate limiting offered by firewalls but often inferior.
+           Attacks that justify ignoring the
+           contents of DNS responses are likely to be attacks on the
+           DNS server itself.
+           They usually should be discarded before the DNS server
+           spends resources make TCP connections or parsing DNS requesets,
+           but that rate limiting must be done before the
+           DNS server sees the requests.
+         </para>
+
+         <para>
+           The maximum size of the table used to track requests and
+           rate limit responses is set with <command>max-table-size</command>.
+           Each entry in the table is between 40 and 80 bytes.
+           The table needs approximately as many entries as the number
+           of requests received per second.
+           The default is 20,000.
+           To reduce the cold start of growing the table,
+           <command>min-table-size</command> (default 500)
+           can set the minimum table size.
+           Enable <command>rate-limit</command> category logging to monitor
+           expansions of the table and inform
+           choices for the initial and maximum table size.
+         </para>
+
+         <para>
+           Use <command>log-only yes</command> to test rate limiting parameters
+           without actually dropping any requests.
+         </para>
+
+         <para>
+           Responses dropped by rate limits are included in the
+           <command>RateDropped</command> and <command>QryDropped</command>
+           statistics.
+           Responses that truncated by rate limits are included in
+           <command>RateSlipped</command> and <command>RespTruncated</command>.
+       </sect3>
       </sect2>
 
       <sect2 id="server_statement_grammar">
@@ -14650,6 +14920,32 @@ HOST-127.EXAMPLE. MX 0 .
                      </para>
                    </entry>
                  </row>
+                 <row rowsep="0">
+                   <entry colname="1">
+                     <para><command>RateDropped</command></para>
+                   </entry>
+                   <entry colname="2">
+                     <para><command></command></para>
+                   </entry>
+                   <entry colname="3">
+                     <para>
+                       Responses dropped by rate limits.
+                     </para>
+                   </entry>
+                 </row>
+                 <row rowsep="0">
+                   <entry colname="1">
+                     <para><command>RateSlipped</command></para>
+                   </entry>
+                   <entry colname="2">
+                     <para><command></command></para>
+                   </entry>
+                   <entry colname="3">
+                     <para>
+                       Responses truncated by rate limits.
+                     </para>
+                   </entry>
+                 </row>
                </tbody>
               </tgroup>
             </informaltable>
index b712ab1cd874e37d8f99351e1da9245f19966e34..a3e3e9a4e86ac8debcb957e13b0e97862fa650c0 100644 (file)
@@ -55,6 +55,8 @@ DSTOBJS =     @DST_EXTRA_OBJS@ @OPENSSLLINKOBJS@ \
                dst_api.@O@ dst_lib.@O@ dst_parse.@O@ dst_result.@O@ \
                gssapi_link.@O@ gssapictx.@O@ hmac_link.@O@ key.@O@ 
 
+RRLOBJS =      rrl.@O@
+
 # Alphabetically
 DNSOBJS =      acache.@O@ acl.@O@ adb.@O@ byaddr.@O@ \
                cache.@O@ callbacks.@O@ clientinfo.@O@ compress.@O@ \
@@ -67,14 +69,14 @@ DNSOBJS =   acache.@O@ acl.@O@ adb.@O@ byaddr.@O@ \
                portlist.@O@ private.@O@ \
                rbt.@O@ rbtdb.@O@ rbtdb64.@O@ rcode.@O@ rdata.@O@ \
                rdatalist.@O@ rdataset.@O@ rdatasetiter.@O@ rdataslab.@O@ \
-               request.@O@ resolver.@O@ result.@O@ rootns.@O@ rpz.@O@ \
-               rriterator.@O@ sdb.@O@ \
+               request.@O@ resolver.@O@ result.@O@ rootns.@O@ \
+               rpz.@O@ rriterator.@O@ sdb.@O@ \
                sdlz.@O@ soa.@O@ ssu.@O@ ssu_external.@O@ \
                stats.@O@ tcpmsg.@O@ time.@O@ timer.@O@ tkey.@O@ \
                tsec.@O@ tsig.@O@ ttl.@O@ update.@O@ validator.@O@ \
                version.@O@ view.@O@ xfrin.@O@ zone.@O@ zonekey.@O@ zt.@O@
 
-OBJS=          ${DNSOBJS} ${OTHEROBJS} ${DSTOBJS}
+OBJS=          ${DNSOBJS} ${OTHEROBJS} ${DSTOBJS} @RRLLINKOBJS@
 
 # Alphabetically
 OPENSSLGOSTLINKSRCS = opensslgost_link.c
@@ -101,7 +103,9 @@ DNSSRCS =   acache.c acl.c adb.c byaddr.c \
                tsec.c tsig.c ttl.c update.c validator.c \
                version.c view.c xfrin.c zone.c zonekey.c zt.c ${OTHERSRCS}
 
-SRCS = ${DSTSRCS} ${DNSSRCS}
+RRLSRCS =      rrl.c
+
+SRCS = ${DSTSRCS} ${DNSSRCS} @RRLLINKSRCS@
 
 SUBDIRS =      include
 TARGETS =      include/dns/enumtype.h include/dns/enumclass.h \
index 3c4df8a45003a941803416c2a67815fdfa190c96..f89ae2502fa7f33f7bd5f1dc974ffebf69e52b22 100644 (file)
@@ -43,6 +43,7 @@ LIBDNS_EXTERNAL_DATA extern isc_logmodule_t dns_modules[];
 #define DNS_LOGCATEGORY_DELEGATION_ONLY        (&dns_categories[10])
 #define DNS_LOGCATEGORY_EDNS_DISABLED  (&dns_categories[11])
 #define DNS_LOGCATEGORY_RPZ            (&dns_categories[12])
+#define DNS_LOGCATEGORY_RRL            (&dns_categories[13])
 
 /* Backwards compatibility. */
 #define DNS_LOGCATEGORY_GENERAL                ISC_LOGCATEGORY_GENERAL
diff --git a/lib/dns/include/dns/rrl.h b/lib/dns/include/dns/rrl.h
new file mode 100644 (file)
index 0000000..ef6b72b
--- /dev/null
@@ -0,0 +1,278 @@
+/*
+ * Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+
+#ifndef DNS_RRL_H
+#define DNS_RRL_H 1
+
+/*
+ * Rate limit DNS responses.
+ */
+
+#include <isc/lang.h>
+
+#include <dns/fixedname.h>
+#include <dns/rdata.h>
+#include <dns/types.h>
+
+ISC_LANG_BEGINDECLS
+
+
+/*
+ * Memory allocation or other failures.
+ */
+#define DNS_RRL_LOG_FAIL       ISC_LOG_WARNING
+/*
+ * dropped or slipped responses.
+ */
+#define DNS_RRL_LOG_DROP       ISC_LOG_INFO
+/*
+ * Major events in dropping or slipping.
+ */
+#define DNS_RRL_LOG_DEBUG1     ISC_LOG_DEBUG(3)
+/*
+ * Limit computations.
+ */
+#define DNS_RRL_LOG_DEBUG2     ISC_LOG_DEBUG(4)
+/*
+ * Even less interesting.
+ */
+#define DNS_RRL_LOG_DEBUG3     ISC_LOG_DEBUG(9)
+
+
+#define DNS_RRL_LOG_ERR_LEN    64
+#define DNS_RRL_LOG_BUF_LEN    (sizeof("would continue limiting") +    \
+                                DNS_RRL_LOG_ERR_LEN +                  \
+                                sizeof(" responses to ") +             \
+                                ISC_NETADDR_FORMATSIZE +               \
+                                sizeof("/128 for IN ") +               \
+                                DNS_RDATATYPE_FORMATSIZE +             \
+                                DNS_NAME_FORMATSIZE)
+
+
+typedef struct dns_rrl_hash dns_rrl_hash_t;
+
+/*
+ * Response types.
+ */
+typedef enum {
+       DNS_RRL_RTYPE_FREE = 0,
+       DNS_RRL_RTYPE_QUERY,
+       DNS_RRL_RTYPE_REFERRAL,
+       DNS_RRL_RTYPE_NODATA,
+       DNS_RRL_RTYPE_NXDOMAIN,
+       DNS_RRL_RTYPE_ERROR,
+       DNS_RRL_RTYPE_ALL,
+       DNS_RRL_RTYPE_TCP,
+} dns_rrl_rtype_t;
+
+/*
+ * A rate limit bucket key.
+ * This should be small to limit the total size of the database.
+ * The hash of the qname should be wide enough to make the probability
+ * of collisions among requests from a single IP address block less than 50%.
+ * We need a 32-bit hash value for 10000 qps (e.g. random qnames forged
+ * by attacker) to collide with legitimate qnames from the target with
+ * probability at most 1%.
+ */
+#define DNS_RRL_MAX_PREFIX  64
+typedef union dns_rrl_key dns_rrl_key_t;
+union dns_rrl_key {
+       struct {
+               isc_uint32_t        ip[DNS_RRL_MAX_PREFIX/32];
+               isc_uint32_t        qname_hash;
+               dns_rdatatype_t     qtype;
+               isc_uint8_t         qclass;
+               dns_rrl_rtype_t     rtype   :4; /* 3 bits + sign bit */
+               isc_boolean_t       ipv6    :1;
+       } s;
+       isc_uint16_t    w[1];
+};
+
+/*
+ * A rate-limit entry.
+ * This should be small to limit the total size of the table of entries.
+ */
+typedef struct dns_rrl_entry dns_rrl_entry_t;
+typedef ISC_LIST(dns_rrl_entry_t) dns_rrl_bin_t;
+struct dns_rrl_entry {
+       ISC_LINK(dns_rrl_entry_t) lru;
+       ISC_LINK(dns_rrl_entry_t) hlink;
+       dns_rrl_key_t   key;
+# define DNS_RRL_RESPONSE_BITS 24
+       signed int      responses   :DNS_RRL_RESPONSE_BITS;
+# define DNS_RRL_QNAMES_BITS   8
+       unsigned int    log_qname   :DNS_RRL_QNAMES_BITS;
+
+# define DNS_RRL_TS_GEN_BITS   2
+       unsigned int    ts_gen      :DNS_RRL_TS_GEN_BITS;
+       isc_boolean_t   ts_valid    :1;
+# define DNS_RRL_HASH_GEN_BITS 1
+       unsigned int    hash_gen    :DNS_RRL_HASH_GEN_BITS;
+       isc_boolean_t   logged      :1;
+# define DNS_RRL_LOG_BITS      11
+       unsigned int    log_secs    :DNS_RRL_LOG_BITS;
+
+# define DNS_RRL_TS_BITS       12
+       unsigned int    ts          :DNS_RRL_TS_BITS;
+
+# define DNS_RRL_MAX_SLIP      10
+       unsigned int    slip_cnt    :4;
+};
+
+#define DNS_RRL_MAX_TIME_TRAVEL        5
+#define DNS_RRL_FOREVER                (1<<DNS_RRL_TS_BITS)
+#define DNS_RRL_MAX_TS         (DNS_RRL_FOREVER - 1)
+
+#define DNS_RRL_MAX_RESPONSES  ((1<<(DNS_RRL_RESPONSE_BITS-1))-1)
+#define DNS_RRL_MAX_WINDOW     3600
+#if DNS_RRL_MAX_WINDOW >= DNS_RRL_MAX_TS
+#error "DNS_RRL_MAX_WINDOW is too large"
+#endif
+#define DNS_RRL_MAX_RATE       1000
+#if DNS_RRL_MAX_RATE >= (DNS_RRL_MAX_RESPONSES / DNS_RRL_MAX_WINDOW)
+#error "DNS_RRL_MAX_rate is too large"
+#endif
+
+#if (1<<DNS_RRL_LOG_BITS) >= DNS_RRL_FOREVER
+#error DNS_RRL_LOG_BITS is too big
+#endif
+#define DNS_RRL_MAX_LOG_SECS   1800
+#if DNS_RRL_MAX_LOG_SECS >= (1<<DNS_RRL_LOG_BITS)
+#error "DNS_RRL_MAX_LOG_SECS is too large"
+#endif
+#define DNS_RRL_STOP_LOG_SECS  60
+#if DNS_RRL_STOP_LOG_SECS >= (1<<DNS_RRL_LOG_BITS)
+#error "DNS_RRL_STOP_LOG_SECS is too large"
+#endif
+
+
+/*
+ * A hash table of rate-limit entries.
+ */
+struct dns_rrl_hash {
+       isc_stdtime_t   check_time;
+       unsigned int    gen         :DNS_RRL_HASH_GEN_BITS;
+       int             length;
+       dns_rrl_bin_t   bins[1];
+};
+
+/*
+ * A block of rate-limit entries.
+ */
+typedef struct dns_rrl_block dns_rrl_block_t;
+struct dns_rrl_block {
+       ISC_LINK(dns_rrl_block_t) link;
+       int             size;
+       dns_rrl_entry_t entries[1];
+};
+
+/*
+ * A rate limited qname buffer.
+ */
+typedef struct dns_rrl_qname_buf dns_rrl_qname_buf_t;
+struct dns_rrl_qname_buf {
+       ISC_LINK(dns_rrl_qname_buf_t) link;
+       const dns_rrl_entry_t *e;
+       unsigned int        index;
+       dns_fixedname_t     qname;
+};
+
+typedef struct dns_rrl_rate dns_rrl_rate_t;
+struct dns_rrl_rate {
+       int         r;
+       int         scaled;
+       const char  *str;
+};
+
+/*
+ * Per-view query rate limit parameters and a pointer to database.
+ */
+typedef struct dns_rrl dns_rrl_t;
+struct dns_rrl {
+       isc_mutex_t     lock;
+       isc_mem_t       *mctx;
+
+       isc_boolean_t   log_only;
+       dns_rrl_rate_t  responses_per_second;
+       dns_rrl_rate_t  referrals_per_second;
+       dns_rrl_rate_t  nodata_per_second;
+       dns_rrl_rate_t  nxdomains_per_second;
+       dns_rrl_rate_t  errors_per_second;
+       dns_rrl_rate_t  all_per_second;
+       dns_rrl_rate_t  slip;
+       int             window;
+       double          qps_scale;
+       int             max_entries;
+
+       dns_acl_t       *exempt;
+
+       int             num_entries;
+
+       int             qps_responses;
+       isc_stdtime_t   qps_time;
+       double          qps;
+
+       unsigned int    probes;
+       unsigned int    searches;
+
+       ISC_LIST(dns_rrl_block_t) blocks;
+       ISC_LIST(dns_rrl_entry_t) lru;
+
+       dns_rrl_hash_t  *hash;
+       dns_rrl_hash_t  *old_hash;
+       unsigned int    hash_gen;
+
+       unsigned int    ts_gen;
+# define DNS_RRL_TS_BASES   (1<<DNS_RRL_TS_GEN_BITS)
+       isc_stdtime_t   ts_bases[DNS_RRL_TS_BASES];
+
+       int             ipv4_prefixlen;
+       isc_uint32_t    ipv4_mask;
+       int             ipv6_prefixlen;
+       isc_uint32_t    ipv6_mask[4];
+
+       isc_stdtime_t   log_stops_time;
+       dns_rrl_entry_t *last_logged;
+       int             num_logged;
+       int             num_qnames;
+       ISC_LIST(dns_rrl_qname_buf_t) qname_free;
+# define DNS_RRL_QNAMES            (1<<DNS_RRL_QNAMES_BITS)
+       dns_rrl_qname_buf_t *qnames[DNS_RRL_QNAMES];
+};
+
+typedef enum {
+       DNS_RRL_RESULT_OK,
+       DNS_RRL_RESULT_DROP,
+       DNS_RRL_RESULT_SLIP,
+} dns_rrl_result_t;
+
+dns_rrl_result_t
+dns_rrl(dns_view_t *view,
+       const isc_sockaddr_t *client_addr, isc_boolean_t is_tcp,
+       dns_rdataclass_t rdclass, dns_rdatatype_t qtype,
+       dns_name_t *qname, isc_result_t resp_result, isc_stdtime_t now,
+       isc_boolean_t wouldlog, char *log_buf, unsigned int log_buf_len);
+
+void
+dns_rrl_view_destroy(dns_view_t *view);
+
+isc_result_t
+dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries);
+
+ISC_LANG_ENDDECLS
+
+#endif /* DNS_RRL_H */
index d0c1931d27bed5c1b3405160603cbc04e258cbd0..704e5fe3e9993a8d0ee25fb67c21ca8fc9e1d942 100644 (file)
@@ -73,6 +73,7 @@
 
 #include <dns/acl.h>
 #include <dns/fixedname.h>
+#include <dns/rrl.h>
 #include <dns/rdatastruct.h>
 #include <dns/rpz.h>
 #include <dns/types.h>
@@ -142,6 +143,7 @@ struct dns_view {
        dns_rbt_t *                     answeracl_exclude;
        dns_rbt_t *                     denyanswernames;
        dns_rbt_t *                     answernames_exclude;
+       dns_rrl_t *                     rrl;
        isc_boolean_t                   provideixfr;
        isc_boolean_t                   requestnsid;
        dns_ttl_t                       maxcachettl;
index c4d644e3899f6879b0cf315f0601e50e4f7ec8d5..0620ba30aba928f3bf245d64d79316e0f7f64569 100644 (file)
@@ -45,6 +45,7 @@ LIBDNS_EXTERNAL_DATA isc_logcategory_t dns_categories[] = {
        { "delegation-only", 0 },
        { "edns-disabled", 0 },
        { "rpz",        0 },
+       { "rate-limit", 0 },
        { NULL,         0 }
 };
 
diff --git a/lib/dns/rrl.c b/lib/dns/rrl.c
new file mode 100644 (file)
index 0000000..96c55fe
--- /dev/null
@@ -0,0 +1,1324 @@
+/*
+ * Copyright (C) 2013  Internet Systems Consortium, Inc. ("ISC")
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
+ * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
+ * AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
+ * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
+ * OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
+ * PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*! \file */
+
+/*
+ * Rate limit DNS responses.
+ */
+
+/* #define ISC_LIST_CHECKINIT */
+
+#include <config.h>
+#include <isc/mem.h>
+#include <isc/net.h>
+#include <isc/netaddr.h>
+#include <isc/print.h>
+
+#include <dns/result.h>
+#include <dns/rcode.h>
+#include <dns/rdatatype.h>
+#include <dns/rdataclass.h>
+#include <dns/log.h>
+#include <dns/rrl.h>
+#include <dns/view.h>
+
+static void
+log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_boolean_t early,
+       char *log_buf, unsigned int log_buf_len);
+
+/*
+ * Get a modulus for a hash function that is tolerably likely to be
+ * relatively prime to most inputs.  Of course, we get a prime for for initial
+ * values not larger than the square of the last prime.  We often get a prime
+ * after that.
+ * This works well in practice for hash tables up to at least 100
+ * times the square of the last prime and better than a multiplicative hash.
+ */
+static int
+hash_divisor(unsigned int initial) {
+       static isc_uint16_t primes[] = {
+                 3,   5,   7,  11,  13,  17,  19,  23,  29,  31,  37,  41,
+                43,  47,  53,  59,  61,  67,  71,  73,  79,  83,  89,  97,
+#if 0
+               101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157,
+               163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227,
+               229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283,
+               293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367,
+               373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439,
+               443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509,
+               521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599,
+               601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661,
+               673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751,
+               757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829,
+               839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919,
+               929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997,1009,
+#endif
+       };
+       int divisions, tries;
+       unsigned int result;
+       isc_uint16_t *pp, p;
+
+       result = initial;
+
+       if (primes[sizeof(primes)/sizeof(primes[0])-1] >= result) {
+               pp = primes;
+               while (*pp < result)
+                       ++pp;
+               return (*pp);
+       }
+
+       if ((result & 1) == 0)
+               ++result;
+
+       divisions = 0;
+       tries = 1;
+       pp = primes;
+       do {
+               p = *pp++;
+               ++divisions;
+               if ((result % p) == 0) {
+                       ++tries;
+                       result += 2;
+                       pp = primes;
+               }
+       } while (pp < &primes[sizeof(primes) / sizeof(primes[0])]);
+
+       if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3))
+               isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                             DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG3,
+                             "%d hash_divisor() divisions in %d tries"
+                             " to get %d from %d",
+                             divisions, tries, result, initial);
+
+       return (result);
+}
+
+/*
+ * Convert a timestamp to a number of seconds in the past.
+ */
+static inline int
+delta_rrl_time(isc_stdtime_t ts, isc_stdtime_t now) {
+       int delta;
+
+       delta = now - ts;
+       if (delta >= 0)
+               return (delta);
+
+       /*
+        * The timestamp is in the future.  That future might result from
+        * re-ordered requests, because we use timestamps on requests
+        * instead of consulting a clock.  Timestamps in the distant future are
+        * assumed to result from clock changes.  When the clock changes to
+        * the past, make existing timestamps appear to be in the past.
+        */
+       if (delta < -DNS_RRL_MAX_TIME_TRAVEL)
+               return (DNS_RRL_FOREVER);
+       return (0);
+}
+
+static inline int
+get_age(const dns_rrl_t *rrl, const dns_rrl_entry_t *e, isc_stdtime_t now) {
+       if (!e->ts_valid)
+               return (DNS_RRL_FOREVER);
+       return (delta_rrl_time(e->ts + rrl->ts_bases[e->ts_gen], now));
+}
+
+static inline void
+set_age(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_stdtime_t now) {
+       dns_rrl_entry_t *e_old;
+       unsigned int ts_gen;
+       int i, ts;
+
+       ts_gen = rrl->ts_gen;
+       ts = now - rrl->ts_bases[ts_gen];
+       if (ts < 0) {
+               if (ts < -DNS_RRL_MAX_TIME_TRAVEL)
+                       ts = DNS_RRL_FOREVER;
+               else
+                       ts = 0;
+       }
+
+       /*
+        * Make a new timestamp base if the current base is too old.
+        * All entries older than DNS_RRL_MAX_WINDOW seconds are ancient,
+        * useless history.  Their timestamps can be treated as if they are
+        * all the same.
+        * We only do arithmetic on more recent timestamps, so bases for
+        * older timestamps can be recycled provided the old timestamps are
+        * marked as ancient history.
+        * This loop is almost always very short because most entries are
+        * recycled after one second and any entries that need to be marked
+        * are older than (DNS_RRL_TS_BASES)*DNS_RRL_MAX_TS seconds.
+        */
+       if (ts >= DNS_RRL_MAX_TS) {
+               ts_gen = (ts_gen + 1) % DNS_RRL_TS_BASES;
+               for (e_old = ISC_LIST_TAIL(rrl->lru), i = 0;
+                    e_old != NULL && (e_old->ts_gen == ts_gen ||
+                                      !ISC_LINK_LINKED(e_old, hlink));
+                    e_old = ISC_LIST_PREV(e_old, lru), ++i)
+               {
+                       e_old->ts_valid = ISC_FALSE;
+               }
+               if (i != 0)
+                       isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                                     DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1,
+                                     "rrl new time base scanned %d entries"
+                                     " at %d for %d %d %d %d",
+                                     i, now, rrl->ts_bases[ts_gen],
+                                     rrl->ts_bases[(ts_gen + 1) %
+                                       DNS_RRL_TS_BASES],
+                                     rrl->ts_bases[(ts_gen + 2) %
+                                       DNS_RRL_TS_BASES],
+                                     rrl->ts_bases[(ts_gen + 3) %
+                                       DNS_RRL_TS_BASES]);
+               rrl->ts_gen = ts_gen;
+               rrl->ts_bases[ts_gen] = now;
+               ts = 0;
+       }
+
+       e->ts_gen = ts_gen;
+       e->ts = ts;
+       e->ts_valid = ISC_TRUE;
+}
+
+static isc_result_t
+expand_entries(dns_rrl_t *rrl, int new) {
+       unsigned int bsize;
+       dns_rrl_block_t *b;
+       dns_rrl_entry_t *e;
+       double rate;
+       int i;
+
+       if (rrl->num_entries + new >= rrl->max_entries &&
+           rrl->max_entries != 0)
+       {
+               new = rrl->max_entries - rrl->num_entries;
+               if (new <= 0)
+                       return (ISC_R_SUCCESS);
+       }
+
+       /*
+        * Log expansions so that the user can tune max-table-size
+        * and min-table-size.
+        */
+       if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) &&
+           rrl->hash != NULL) {
+               rate = rrl->probes;
+               if (rrl->searches != 0)
+                       rate /= rrl->searches;
+               isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                             DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
+                             "increase from %d to %d RRL entries with"
+                             " %d bins; average search length %.1f",
+                             rrl->num_entries, rrl->num_entries+new,
+                             rrl->hash->length, rate);
+       }
+
+       bsize = sizeof(dns_rrl_block_t) + (new-1)*sizeof(dns_rrl_entry_t);
+       b = isc_mem_get(rrl->mctx, bsize);
+       if (b == NULL) {
+               isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                             DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_FAIL,
+                             "isc_mem_get(%d) failed for RRL entries",
+                             bsize);
+               return (ISC_R_NOMEMORY);
+       }
+       memset(b, 0, bsize);
+       b->size = bsize;
+
+       e = b->entries;
+       for (i = 0; i < new; ++i, ++e) {
+               ISC_LINK_INIT(e, hlink);
+               ISC_LIST_INITANDAPPEND(rrl->lru, e, lru);
+       }
+       rrl->num_entries += new;
+       ISC_LIST_INITANDAPPEND(rrl->blocks, b, link);
+
+       return (ISC_R_SUCCESS);
+}
+
+static inline dns_rrl_bin_t *
+get_bin(dns_rrl_hash_t *hash, unsigned int hval) {
+       return (&hash->bins[hval % hash->length]);
+}
+
+static void
+free_old_hash(dns_rrl_t *rrl) {
+       dns_rrl_hash_t *old_hash;
+       dns_rrl_bin_t *old_bin;
+       dns_rrl_entry_t *e, *e_next;
+
+       old_hash = rrl->old_hash;
+       for (old_bin = &old_hash->bins[0];
+            old_bin < &old_hash->bins[old_hash->length];
+            ++old_bin)
+       {
+               for (e = ISC_LIST_HEAD(*old_bin); e != NULL; e = e_next) {
+                       e_next = ISC_LIST_NEXT(e, hlink);
+                       ISC_LINK_INIT(e, hlink);
+               }
+       }
+
+       isc_mem_put(rrl->mctx, old_hash,
+                   sizeof(*old_hash)
+                     + (old_hash->length - 1) * sizeof(old_hash->bins[0]));
+       rrl->old_hash = NULL;
+}
+
+static isc_result_t
+expand_rrl_hash(dns_rrl_t *rrl, isc_stdtime_t now) {
+       dns_rrl_hash_t *hash;
+       int old_bins, new_bins, hsize;
+       double rate;
+
+       if (rrl->old_hash != NULL)
+               free_old_hash(rrl);
+
+       /*
+        * Most searches fail and so go to the end of the chain.
+        * Use a small hash table load factor.
+        */
+       old_bins = (rrl->hash == NULL) ? 0 : rrl->hash->length;
+       new_bins = old_bins/8 + old_bins;
+       if (new_bins < rrl->num_entries)
+               new_bins = rrl->num_entries;
+       new_bins = hash_divisor(new_bins);
+
+       hsize = sizeof(dns_rrl_hash_t) + (new_bins-1)*sizeof(hash->bins[0]);
+       hash = isc_mem_get(rrl->mctx, hsize);
+       if (hash == NULL) {
+               isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                             DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_FAIL,
+                             "isc_mem_get(%d) failed for"
+                             " RRL hash table",
+                             hsize);
+               return (ISC_R_NOMEMORY);
+       }
+       memset(hash, 0, hsize);
+       hash->length = new_bins;
+       rrl->hash_gen ^= 1;
+       hash->gen = rrl->hash_gen;
+
+       if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP) && old_bins != 0) {
+               rate = rrl->probes;
+               if (rrl->searches != 0)
+                       rate /= rrl->searches;
+               isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                             DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
+                             "increase from %d to %d RRL bins for"
+                             " %d entries; average search length %.1f",
+                             old_bins, new_bins, rrl->num_entries, rate);
+       }
+
+       rrl->old_hash = rrl->hash;
+       if (rrl->old_hash != NULL)
+               rrl->old_hash->check_time = now;
+       rrl->hash = hash;
+
+       return (ISC_R_SUCCESS);
+}
+
+static void
+ref_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, int probes, isc_stdtime_t now) {
+       /*
+        * Make the entry most recently used.
+        */
+       if (ISC_LIST_HEAD(rrl->lru) != e) {
+               if (e == rrl->last_logged)
+                       rrl->last_logged = ISC_LIST_PREV(e, lru);
+               ISC_LIST_UNLINK(rrl->lru, e, lru);
+               ISC_LIST_PREPEND(rrl->lru, e, lru);
+       }
+
+       /*
+        * Expand the hash table if it is time and necessary.
+        * This will leave the newly referenced entry in a chain in the
+        * old hash table.  It will migrate to the new hash table the next
+        * time it is used or be cut loose when the old hash table is destroyed.
+        */
+       rrl->probes += probes;
+       ++rrl->searches;
+       if (rrl->searches > 100 &&
+           delta_rrl_time(rrl->hash->check_time, now) > 1) {
+               if (rrl->probes/rrl->searches > 2)
+                       expand_rrl_hash(rrl, now);
+               rrl->hash->check_time = now;
+               rrl->probes = 0;
+               rrl->searches = 0;
+       }
+}
+
+static inline isc_boolean_t
+key_cmp(const dns_rrl_key_t *a, const dns_rrl_key_t *b) {
+       if (memcmp(a, b, sizeof(dns_rrl_key_t)) == 0)
+               return (ISC_TRUE);
+       return (ISC_FALSE);
+}
+
+static inline isc_uint32_t
+hash_key(const dns_rrl_key_t *key) {
+       isc_uint32_t hval;
+       int i;
+
+       hval = key->w[0];
+       for (i = sizeof(*key) / sizeof(key->w[0]) - 1; i >= 0; --i) {
+               hval = key->w[i] + (hval<<1);
+       }
+       return (hval);
+}
+
+/*
+ * Construct the hash table key.
+ * Use a hash of the DNS query name to save space in the database.
+ * Collisions result in legitimate rate limiting responses for one
+ * query name also limiting responses for other names to the
+ * same client.  This is rare and benign enough given the large
+ * space costs compared to keeping the entire name in the database
+ * entry or the time costs of dynamic allocation.
+ */
+static void
+make_key(const dns_rrl_t *rrl, dns_rrl_key_t *key,
+        const isc_sockaddr_t *client_addr,
+        dns_rdatatype_t qtype, dns_name_t *qname, dns_rdataclass_t qclass,
+        dns_rrl_rtype_t rtype)
+{
+       dns_name_t base;
+       dns_offsets_t base_offsets;
+       int labels, i;
+
+       memset(key, 0, sizeof(*key));
+
+       key->s.rtype = rtype;
+       if (rtype == DNS_RRL_RTYPE_QUERY) {
+               key->s.qtype = qtype;
+               key->s.qclass = qclass & 0xff;
+       } else if (rtype == DNS_RRL_RTYPE_REFERRAL ||
+                  rtype == DNS_RRL_RTYPE_NODATA) {
+               /*
+                * Because there is no qtype in the empty answer sections of
+                * referral and NODATA responses, count them as the same.
+                */
+               key->s.qclass = qclass & 0xff;
+       }
+
+       if (qname != NULL && qname->labels != 0) {
+               /*
+                * Ignore the first label of wildcards.
+                */
+               if ((qname->attributes & DNS_NAMEATTR_WILDCARD) != 0 &&
+                   (labels = dns_name_countlabels(qname)) > 1)
+               {
+                       dns_name_init(&base, base_offsets);
+                       dns_name_getlabelsequence(qname, 1, labels-1, &base);
+                       key->s.qname_hash = dns_name_hashbylabel(&base,
+                                                       ISC_FALSE);
+               } else {
+                       key->s.qname_hash = dns_name_hashbylabel(qname,
+                                                       ISC_FALSE);
+               }
+       }
+
+       switch (client_addr->type.sa.sa_family) {
+       case AF_INET:
+               key->s.ip[0] = (client_addr->type.sin.sin_addr.s_addr &
+                             rrl->ipv4_mask);
+               break;
+       case AF_INET6:
+               key->s.ipv6 = ISC_TRUE;
+               memcpy(key->s.ip, &client_addr->type.sin6.sin6_addr,
+                      sizeof(key->s.ip));
+               for (i = 0; i < DNS_RRL_MAX_PREFIX/32; ++i)
+                       key->s.ip[i] &= rrl->ipv6_mask[i];
+               break;
+       }
+}
+
+static inline dns_rrl_rate_t *
+get_rate(dns_rrl_t *rrl, dns_rrl_rtype_t rtype) {
+       switch (rtype) {
+       case DNS_RRL_RTYPE_QUERY:
+               return (&rrl->responses_per_second);
+       case DNS_RRL_RTYPE_REFERRAL:
+               return (&rrl->referrals_per_second);
+       case DNS_RRL_RTYPE_NODATA:
+               return (&rrl->nodata_per_second);
+       case DNS_RRL_RTYPE_NXDOMAIN:
+               return (&rrl->nxdomains_per_second);
+       case DNS_RRL_RTYPE_ERROR:
+               return (&rrl->errors_per_second);
+       case DNS_RRL_RTYPE_ALL:
+               return (&rrl->all_per_second);
+       default:
+               INSIST(0);
+       }
+       return (NULL);
+}
+
+static int
+response_balance(dns_rrl_t *rrl, const dns_rrl_entry_t *e, int age) {
+       dns_rrl_rate_t *ratep;
+       int balance, rate;
+
+       if (e->key.s.rtype == DNS_RRL_RTYPE_TCP) {
+               rate = 1;
+       } else {
+               ratep = get_rate(rrl, e->key.s.rtype);
+               rate = ratep->scaled;
+       }
+
+       balance = e->responses + age * rate;
+       if (balance > rate)
+               balance = rate;
+       return (balance);
+}
+
+/*
+ * Search for an entry for a response and optionally create it.
+ */
+static dns_rrl_entry_t *
+get_entry(dns_rrl_t *rrl, const isc_sockaddr_t *client_addr,
+         dns_rdataclass_t qclass, dns_rdatatype_t qtype, dns_name_t *qname,
+         dns_rrl_rtype_t rtype, isc_stdtime_t now, isc_boolean_t create,
+         char *log_buf, unsigned int log_buf_len)
+{
+       dns_rrl_key_t key;
+       isc_uint32_t hval;
+       dns_rrl_entry_t *e;
+       dns_rrl_hash_t *hash;
+       dns_rrl_bin_t *new_bin, *old_bin;
+       int probes, age;
+
+       make_key(rrl, &key, client_addr, qtype, qname, qclass, rtype);
+       hval = hash_key(&key);
+
+       /*
+        * Look for the entry in the current hash table.
+        */
+       new_bin = get_bin(rrl->hash, hval);
+       probes = 1;
+       e = ISC_LIST_HEAD(*new_bin);
+       while (e != NULL) {
+               if (key_cmp(&e->key, &key)) {
+                       ref_entry(rrl, e, probes, now);
+                       return (e);
+               }
+               ++probes;
+               e = ISC_LIST_NEXT(e, hlink);
+       }
+
+       /*
+        * Look in the old hash table.
+        */
+       if (rrl->old_hash != NULL) {
+               old_bin = get_bin(rrl->old_hash, hval);
+               e = ISC_LIST_HEAD(*old_bin);
+               while (e != NULL) {
+                       if (key_cmp(&e->key, &key)) {
+                               ISC_LIST_UNLINK(*old_bin, e, hlink);
+                               ISC_LIST_PREPEND(*new_bin, e, hlink);
+                               e->hash_gen = rrl->hash_gen;
+                               ref_entry(rrl, e, probes, now);
+                               return (e);
+                       }
+                       e = ISC_LIST_NEXT(e, hlink);
+               }
+
+               /*
+                * Discard prevous hash table when all of its entries are old.
+                */
+               age = delta_rrl_time(rrl->old_hash->check_time, now);
+               if (age > rrl->window)
+                       free_old_hash(rrl);
+       }
+
+       if (!create)
+               return (NULL);
+
+       /*
+        * The entry does not exist, so create it by finding a free entry.
+        * Keep currently penalized and logged entries.
+        * Try to make more entries if none are idle.
+        * Steal the oldest entry if we cannot create more.
+        */
+       for (e = ISC_LIST_TAIL(rrl->lru);
+            e != NULL;
+            e = ISC_LIST_PREV(e, lru))
+       {
+               if (!ISC_LINK_LINKED(e, hlink))
+                       break;
+               age = get_age(rrl, e, now);
+               if (age <= 1) {
+                       e = NULL;
+                       break;
+               }
+               if (!e->logged && response_balance(rrl, e, age) > 0)
+                       break;
+       }
+       if (e == NULL) {
+               expand_entries(rrl, ISC_MIN((rrl->num_entries+1)/2, 1000));
+               e = ISC_LIST_TAIL(rrl->lru);
+       }
+       if (e->logged)
+               log_end(rrl, e, ISC_TRUE, log_buf, log_buf_len);
+       if (ISC_LINK_LINKED(e, hlink)) {
+               if (e->hash_gen == rrl->hash_gen)
+                       hash = rrl->hash;
+               else
+                       hash = rrl->old_hash;
+               old_bin = get_bin(hash, hash_key(&e->key));
+               ISC_LIST_UNLINK(*old_bin, e, hlink);
+       }
+       ISC_LIST_PREPEND(*new_bin, e, hlink);
+       e->hash_gen = rrl->hash_gen;
+       e->key = key;
+       e->ts_valid = ISC_FALSE;
+       ref_entry(rrl, e, probes, now);
+       return (e);
+}
+
+static void
+debit_log(const dns_rrl_entry_t *e, int age, const char *action) {
+       char buf[sizeof("age=12345678")];
+       const char *age_str;
+
+       if (age == DNS_RRL_FOREVER) {
+               age_str = "";
+       } else {
+               snprintf(buf, sizeof(buf), "age=%d", age);
+               age_str = buf;
+       }
+       isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                     DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG3,
+                     "rrl %08x %6s  responses=%-3d %s",
+                     hash_key(&e->key), age_str, e->responses, action);
+}
+
+static inline dns_rrl_result_t
+debit_rrl_entry(dns_rrl_t *rrl, dns_rrl_entry_t *e, double qps, double scale,
+               const isc_sockaddr_t *client_addr, isc_stdtime_t now,
+               char *log_buf, unsigned int log_buf_len)
+{
+       int rate, new_rate, slip, new_slip, age, log_secs, min;
+       dns_rrl_rate_t *ratep;
+       dns_rrl_entry_t const *credit_e;
+
+       /*
+        * Pick the rate counter.
+        * Optionally adjust the rate by the estimated query/second rate.
+        */
+       ratep = get_rate(rrl, e->key.s.rtype);
+       rate = ratep->r;
+       if (rate == 0)
+               return (DNS_RRL_RESULT_OK);
+
+       if (scale < 1.0) {
+               /*
+                * The limit for clients that have used TCP is not scaled.
+                */
+               credit_e = get_entry(rrl, client_addr,
+                                    0, dns_rdatatype_none, NULL,
+                                    DNS_RRL_RTYPE_TCP, now, ISC_FALSE,
+                                    log_buf, log_buf_len);
+               if (credit_e != NULL) {
+                       age = get_age(rrl, e, now);
+                       if (age < rrl->window)
+                               scale = 1.0;
+               }
+       }
+       if (scale < 1.0) {
+               new_rate = (int) (rate * scale);
+               if (new_rate < 1)
+                       new_rate = 1;
+               if (ratep->scaled != new_rate) {
+                       isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                                     DNS_LOGMODULE_REQUEST,
+                                     DNS_RRL_LOG_DEBUG1,
+                                     "%d qps scaled %s by %.2f"
+                                     " from %d to %d",
+                                     (int)qps, ratep->str, scale,
+                                     rate, new_rate);
+                       rate = new_rate;
+                       ratep->scaled = rate;
+               }
+       }
+
+       min = -rrl->window * rate;
+
+       /*
+        * Treat time jumps into the recent past as no time.
+        * Treat entries older than the window as if they were just created
+        * Credit other entries.
+        */
+       age = get_age(rrl, e, now);
+       if (age > 0) {
+               /*
+                * Credit tokens earned during elapsed time.
+                */
+               if (age > rrl->window) {
+                       e->responses = rate;
+                       e->slip_cnt = 0;
+               } else {
+                       e->responses += rate*age;
+                       if (e->responses > rate) {
+                               e->responses = rate;
+                               e->slip_cnt = 0;
+                       }
+               }
+               /*
+                * Find the seconds since last log message without overflowing
+                * small counter.  This counter is reset when an entry is
+                * created.  It is not necessarily reset when some requests
+                * are answered provided other requests continue to be dropped
+                * or slipped.  This can happen when the request rate is just
+                * at the limit.
+                */
+               if (e->logged) {
+                       log_secs = e->log_secs;
+                       log_secs += age;
+                       if (log_secs > DNS_RRL_MAX_LOG_SECS || log_secs < 0)
+                               log_secs = DNS_RRL_MAX_LOG_SECS;
+                       e->log_secs = log_secs;
+               }
+       }
+       set_age(rrl, e, now);
+
+       /*
+        * Debit the entry for this response.
+        */
+       if (--e->responses >= 0) {
+               if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3))
+                       debit_log(e, age, "");
+               return (DNS_RRL_RESULT_OK);
+       }
+
+       if (e->responses < min)
+               e->responses = min;
+
+       /*
+        * Drop this response unless it should slip or leak.
+        */
+       slip = rrl->slip.r;
+       if (slip > 2 && scale < 1.0) {
+               new_slip = (int) (slip * scale);
+               if (new_slip < 2)
+                       new_slip = 2;
+               if (rrl->slip.scaled != new_slip) {
+                       isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                                     DNS_LOGMODULE_REQUEST,
+                                     DNS_RRL_LOG_DEBUG1,
+                                     "%d qps scaled slip"
+                                     " by %.2f from %d to %d",
+                                     (int)qps, scale,
+                                     slip, new_slip);
+                       slip = new_slip;
+                       rrl->slip.scaled = slip;
+               }
+       }
+       if (slip != 0 && e->key.s.rtype != DNS_RRL_RTYPE_ALL) {
+               if (e->slip_cnt++ == 0) {
+                       if ((int) e->slip_cnt >= slip)
+                               e->slip_cnt = 0;
+                       if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3))
+                               debit_log(e, age, "slip");
+                       return (DNS_RRL_RESULT_SLIP);
+               } else if ((int) e->slip_cnt >= slip) {
+                       e->slip_cnt = 0;
+               }
+       }
+
+       if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG3))
+               debit_log(e, age, "drop");
+       return (DNS_RRL_RESULT_DROP);
+}
+
+static inline dns_rrl_qname_buf_t *
+get_qname(dns_rrl_t *rrl, const dns_rrl_entry_t *e) {
+       dns_rrl_qname_buf_t *qbuf;
+
+       qbuf = rrl->qnames[e->log_qname];
+       if (qbuf == NULL || qbuf->e != e)
+               return (NULL);
+       return (qbuf);
+}
+
+static inline void
+free_qname(dns_rrl_t *rrl, dns_rrl_entry_t *e) {
+       dns_rrl_qname_buf_t *qbuf;
+
+       qbuf = get_qname(rrl, e);
+       if (qbuf != NULL) {
+               qbuf->e = NULL;
+               ISC_LIST_APPEND(rrl->qname_free, qbuf, link);
+       }
+}
+
+static void
+add_log_str(isc_buffer_t *lb, const char *str, unsigned int str_len) {
+       isc_region_t region;
+
+       isc_buffer_availableregion(lb, &region);
+       if (str_len >= region.length) {
+               if (region.length <= 0)
+                       return;
+               str_len = region.length;
+       }
+       memcpy(region.base, str, str_len);
+       isc_buffer_add(lb, str_len);
+}
+
+#define ADD_LOG_CSTR(eb, s) add_log_str(eb, s, sizeof(s)-1)
+
+/*
+ * Build strings for the logs
+ */
+static void
+make_log_buf(dns_rrl_t *rrl, dns_rrl_entry_t *e,
+            const char *str1, const char *str2, isc_boolean_t plural,
+            dns_name_t *qname, isc_boolean_t save_qname,
+            dns_rrl_result_t rrl_result, isc_result_t resp_result,
+            char *log_buf, unsigned int log_buf_len)
+{
+       isc_buffer_t lb;
+       dns_rrl_qname_buf_t *qbuf;
+       isc_netaddr_t cidr;
+       char strbuf[ISC_MAX(sizeof("/123"), sizeof("  (12345678)"))];
+       const char *rstr;
+       isc_result_t msg_result;
+
+       if (log_buf_len <= 1) {
+               if (log_buf_len == 1)
+                       log_buf[0] = '\0';
+               return;
+       }
+       isc_buffer_init(&lb, log_buf, log_buf_len-1);
+
+       if (str1 != NULL)
+               add_log_str(&lb, str1, strlen(str1));
+       if (str2 != NULL)
+               add_log_str(&lb, str2, strlen(str2));
+
+       switch (rrl_result) {
+       case DNS_RRL_RESULT_OK:
+               break;
+       case DNS_RRL_RESULT_DROP:
+               ADD_LOG_CSTR(&lb, "drop ");
+               break;
+       case DNS_RRL_RESULT_SLIP:
+               ADD_LOG_CSTR(&lb, "slip ");
+               break;
+       default:
+               INSIST(0);
+               break;
+       }
+
+       switch (e->key.s.rtype) {
+       case DNS_RRL_RTYPE_QUERY:
+               break;
+       case DNS_RRL_RTYPE_REFERRAL:
+               ADD_LOG_CSTR(&lb, "referral ");
+               break;
+       case DNS_RRL_RTYPE_NODATA:
+               ADD_LOG_CSTR(&lb, "NODATA ");
+               break;
+       case DNS_RRL_RTYPE_NXDOMAIN:
+               ADD_LOG_CSTR(&lb, "NXDOMAIN ");
+               break;
+       case DNS_RRL_RTYPE_ERROR:
+               if (resp_result == ISC_R_SUCCESS) {
+                       ADD_LOG_CSTR(&lb, "error ");
+               } else {
+                       rstr = isc_result_totext(resp_result);
+                       add_log_str(&lb, rstr, strlen(rstr));
+                       ADD_LOG_CSTR(&lb, " error ");
+               }
+               break;
+       case DNS_RRL_RTYPE_ALL:
+               ADD_LOG_CSTR(&lb, "all ");
+               break;
+       default:
+               INSIST(0);
+       }
+
+       if (plural)
+               ADD_LOG_CSTR(&lb, "responses to ");
+       else
+               ADD_LOG_CSTR(&lb, "response to ");
+
+       memset(&cidr, 0, sizeof(cidr));
+       if (e->key.s.ipv6) {
+               snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv6_prefixlen);
+               cidr.family = AF_INET6;
+               memset(&cidr.type.in6, 0,  sizeof(cidr.type.in6));
+               memcpy(&cidr.type.in6, e->key.s.ip, sizeof(e->key.s.ip));
+       } else {
+               snprintf(strbuf, sizeof(strbuf), "/%d", rrl->ipv4_prefixlen);
+               cidr.family = AF_INET;
+               cidr.type.in.s_addr = e->key.s.ip[0];
+       }
+       msg_result = isc_netaddr_totext(&cidr, &lb);
+       if (msg_result != ISC_R_SUCCESS)
+               ADD_LOG_CSTR(&lb, "?");
+       add_log_str(&lb, strbuf, strlen(strbuf));
+
+       if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY ||
+           e->key.s.rtype == DNS_RRL_RTYPE_REFERRAL ||
+           e->key.s.rtype == DNS_RRL_RTYPE_NODATA ||
+           e->key.s.rtype == DNS_RRL_RTYPE_NXDOMAIN) {
+               qbuf = get_qname(rrl, e);
+               if (save_qname && qbuf == NULL &&
+                   qname != NULL && dns_name_isabsolute(qname)) {
+                       /*
+                        * Capture the qname for the "stop limiting" message.
+                        */
+                       qbuf = ISC_LIST_TAIL(rrl->qname_free);
+                       if (qbuf != NULL) {
+                               ISC_LIST_UNLINK(rrl->qname_free, qbuf, link);
+                       } else if (rrl->num_qnames < DNS_RRL_QNAMES) {
+                               qbuf = isc_mem_get(rrl->mctx, sizeof(*qbuf));
+                               if (qbuf != NULL) {
+                                       memset(qbuf, 0, sizeof(*qbuf));
+                                       ISC_LINK_INIT(qbuf, link);
+                                       qbuf->index = rrl->num_qnames;
+                                       rrl->qnames[rrl->num_qnames++] = qbuf;
+                               } else {
+                                       isc_log_write(dns_lctx,
+                                                     DNS_LOGCATEGORY_RRL,
+                                                     DNS_LOGMODULE_REQUEST,
+                                                     DNS_RRL_LOG_FAIL,
+                                                     "isc_mem_get(%d)"
+                                                     " failed for RRL qname",
+                                                     (int)sizeof(*qbuf));
+                               }
+                       }
+                       if (qbuf != NULL) {
+                               e->log_qname = qbuf->index;
+                               qbuf->e = e;
+                               dns_fixedname_init(&qbuf->qname);
+                               dns_name_copy(qname,
+                                             dns_fixedname_name(&qbuf->qname),
+                                             NULL);
+                       }
+               }
+               if (qbuf != NULL)
+                       qname = dns_fixedname_name(&qbuf->qname);
+               if (qname != NULL) {
+                       ADD_LOG_CSTR(&lb, " for ");
+                       (void)dns_name_totext(qname, ISC_TRUE, &lb);
+               } else {
+                       ADD_LOG_CSTR(&lb, " for (?)");
+               }
+               if (e->key.s.rtype != DNS_RRL_RTYPE_NXDOMAIN) {
+                       ADD_LOG_CSTR(&lb, " ");
+                       (void)dns_rdataclass_totext(e->key.s.qclass, &lb);
+                       if (e->key.s.rtype == DNS_RRL_RTYPE_QUERY) {
+                               ADD_LOG_CSTR(&lb, " ");
+                               (void)dns_rdatatype_totext(e->key.s.qtype, &lb);
+                       }
+               }
+               snprintf(strbuf, sizeof(strbuf), "  (%08x)",
+                        e->key.s.qname_hash);
+               add_log_str(&lb, strbuf, strlen(strbuf));
+       }
+
+       /*
+        * We saved room for '\0'.
+        */
+       log_buf[isc_buffer_usedlength(&lb)] = '\0';
+}
+
+static void
+log_end(dns_rrl_t *rrl, dns_rrl_entry_t *e, isc_boolean_t early,
+       char *log_buf, unsigned int log_buf_len)
+{
+       if (e->logged) {
+               make_log_buf(rrl, e,
+                            early ? "*" : NULL,
+                            rrl->log_only ? "would stop limiting "
+                                          : "stop limiting ",
+                            ISC_TRUE, NULL, ISC_FALSE,
+                            DNS_RRL_RESULT_OK, ISC_R_SUCCESS,
+                            log_buf, log_buf_len);
+               isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                             DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
+                             "%s", log_buf);
+               free_qname(rrl, e);
+               e->logged = ISC_FALSE;
+               --rrl->num_logged;
+       }
+}
+
+/*
+ * Log messages for streams that have stopped being rate limited.
+ */
+static void
+log_stops(dns_rrl_t *rrl, isc_stdtime_t now, int limit,
+         char *log_buf, unsigned int log_buf_len)
+{
+       dns_rrl_entry_t *e;
+       int age;
+
+       for (e = rrl->last_logged; e != NULL; e = ISC_LIST_PREV(e, lru)) {
+               if (!e->logged)
+                       continue;
+               if (now != 0) {
+                       age = get_age(rrl, e, now);
+                       if (age < DNS_RRL_STOP_LOG_SECS ||
+                           response_balance(rrl, e, age) < 0)
+                               break;
+               }
+
+               log_end(rrl, e, now == 0, log_buf, log_buf_len);
+               if (rrl->num_logged <= 0)
+                       break;
+
+               /*
+                * Too many messages could stall real work.
+                */
+               if (--limit < 0) {
+                       rrl->last_logged = ISC_LIST_PREV(e, lru);
+                       return;
+               }
+       }
+       if (e == NULL) {
+               INSIST(rrl->num_logged == 0);
+               rrl->log_stops_time = now;
+       }
+       rrl->last_logged = e;
+}
+
+/*
+ * Main rate limit interface.
+ */
+dns_rrl_result_t
+dns_rrl(dns_view_t *view,
+       const isc_sockaddr_t *client_addr, isc_boolean_t is_tcp,
+       dns_rdataclass_t qclass, dns_rdatatype_t qtype,
+       dns_name_t *qname, isc_result_t resp_result, isc_stdtime_t now,
+       isc_boolean_t wouldlog, char *log_buf, unsigned int log_buf_len)
+{
+       dns_rrl_t *rrl;
+       dns_rrl_rtype_t rtype;
+       dns_rrl_entry_t *e;
+       isc_netaddr_t netclient;
+       int secs;
+       double qps, scale;
+       int exempt_match;
+       isc_result_t result;
+       dns_rrl_result_t rrl_result;
+
+       INSIST(log_buf != NULL && log_buf_len > 0);
+
+       rrl = view->rrl;
+       if (rrl->exempt != NULL) {
+               isc_netaddr_fromsockaddr(&netclient, client_addr);
+               result = dns_acl_match(&netclient, NULL, rrl->exempt,
+                                      &view->aclenv, &exempt_match, NULL);
+               if (result == ISC_R_SUCCESS && exempt_match > 0)
+                       return (DNS_RRL_RESULT_OK);
+       }
+
+       LOCK(&rrl->lock);
+
+       /*
+        * Estimate total query per second rate when scaling by qps.
+        */
+       if (rrl->qps_scale == 0) {
+               qps = 0.0;
+               scale = 1.0;
+       } else {
+               ++rrl->qps_responses;
+               secs = delta_rrl_time(rrl->qps_time, now);
+               if (secs <= 0) {
+                       qps = rrl->qps;
+               } else {
+                       qps = (1.0*rrl->qps_responses) / secs;
+                       if (secs >= rrl->window) {
+                               if (isc_log_wouldlog(dns_lctx,
+                                                    DNS_RRL_LOG_DEBUG3))
+                                       isc_log_write(dns_lctx,
+                                                     DNS_LOGCATEGORY_RRL,
+                                                     DNS_LOGMODULE_REQUEST,
+                                                     DNS_RRL_LOG_DEBUG3,
+                                                     "%d responses/%d seconds"
+                                                     " = %d qps",
+                                                     rrl->qps_responses, secs,
+                                                     (int)qps);
+                               rrl->qps = qps;
+                               rrl->qps_responses = 0;
+                               rrl->qps_time = now;
+                       } else if (qps < rrl->qps) {
+                               qps = rrl->qps;
+                       }
+               }
+               scale = rrl->qps_scale / qps;
+       }
+
+       /*
+        * Do maintenance once per second.
+        */
+       if (rrl->num_logged > 0 && rrl->log_stops_time != now)
+               log_stops(rrl, now, 8, log_buf, log_buf_len);
+
+       /*
+        * Notice TCP responses when scaling limits by qps.
+        * Do not try to rate limit TCP responses.
+        */
+       if (is_tcp) {
+               if (scale < 1.0) {
+                       e = get_entry(rrl, client_addr,
+                                     0, dns_rdatatype_none, NULL,
+                                     DNS_RRL_RTYPE_TCP, now, ISC_TRUE,
+                                     log_buf, log_buf_len);
+                       if (e != NULL) {
+                               e->responses = -(rrl->window+1);
+                               set_age(rrl, e, now);
+                       }
+               }
+               UNLOCK(&rrl->lock);
+               return (ISC_R_SUCCESS);
+       }
+
+       /*
+        * Find the right kind of entry, creating it if necessary.
+        * If that is impossible, then nothing more can be done
+        */
+       switch (resp_result) {
+       case ISC_R_SUCCESS:
+               rtype = DNS_RRL_RTYPE_QUERY;
+               break;
+       case DNS_R_DELEGATION:
+               rtype = DNS_RRL_RTYPE_REFERRAL;
+               break;
+       case DNS_R_NXRRSET:
+               rtype = DNS_RRL_RTYPE_NODATA;
+               break;
+       case DNS_R_NXDOMAIN:
+               rtype = DNS_RRL_RTYPE_NXDOMAIN;
+               break;
+       default:
+               rtype = DNS_RRL_RTYPE_ERROR;
+               break;
+       }
+       e = get_entry(rrl, client_addr, qclass, qtype, qname, rtype,
+                     now, ISC_TRUE, log_buf, log_buf_len);
+       if (e == NULL) {
+               UNLOCK(&rrl->lock);
+               return (DNS_RRL_RESULT_OK);
+       }
+
+       if (isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DEBUG1)) {
+               /*
+                * Do not worry about speed or releasing the lock.
+                * This message appears before messages from debit_rrl_entry().
+                */
+               make_log_buf(rrl, e, "consider limiting ", NULL, ISC_FALSE,
+                            qname, ISC_FALSE, DNS_RRL_RESULT_OK, resp_result,
+                            log_buf, log_buf_len);
+               isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                             DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DEBUG1,
+                             "%s", log_buf);
+       }
+
+       rrl_result = debit_rrl_entry(rrl, e, qps, scale, client_addr, now,
+                                    log_buf, log_buf_len);
+
+       if (rrl->all_per_second.r != 0) {
+               /*
+                * We must debit the all-per-second token bucket if we have
+                * an all-per-second limit for the IP address.
+                * The all-per-second limit determines the log message
+                * when both limits are hit.
+                * The response limiting must continue if the
+                * all-per-second limiting lapses.
+                */
+               dns_rrl_entry_t *e_all;
+               dns_rrl_result_t rrl_all_result;
+
+               e_all = get_entry(rrl, client_addr,
+                                 0, dns_rdatatype_none, NULL,
+                                 DNS_RRL_RTYPE_ALL, now, ISC_TRUE,
+                                 log_buf, log_buf_len);
+               if (e_all == NULL) {
+                       UNLOCK(&rrl->lock);
+                       return (DNS_RRL_RESULT_OK);
+               }
+               rrl_all_result = debit_rrl_entry(rrl, e_all, qps, scale,
+                                                client_addr, now,
+                                                log_buf, log_buf_len);
+               if (rrl_all_result != DNS_RRL_RESULT_OK) {
+                       int level;
+
+                       e = e_all;
+                       rrl_result = rrl_all_result;
+                       if (rrl_result == DNS_RRL_RESULT_OK)
+                               level = DNS_RRL_LOG_DEBUG2;
+                       else
+                               level = DNS_RRL_LOG_DEBUG1;
+                       if (isc_log_wouldlog(dns_lctx, level)) {
+                               make_log_buf(rrl, e,
+                                            "prefer all-per-second limiting ",
+                                            NULL, ISC_TRUE, qname, ISC_FALSE,
+                                            DNS_RRL_RESULT_OK, resp_result,
+                                            log_buf, log_buf_len);
+                               isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                                             DNS_LOGMODULE_REQUEST, level,
+                                             "%s", log_buf);
+                       }
+               }
+       }
+
+       if (rrl_result == DNS_RRL_RESULT_OK) {
+               UNLOCK(&rrl->lock);
+               return (DNS_RRL_RESULT_OK);
+       }
+
+       /*
+        * Log occassionally in the rate-limit category.
+        */
+       if ((!e->logged || e->log_secs >= DNS_RRL_MAX_LOG_SECS) &&
+           isc_log_wouldlog(dns_lctx, DNS_RRL_LOG_DROP)) {
+               make_log_buf(rrl, e, rrl->log_only ? "would " : NULL,
+                            e->logged ? "continue limiting " : "limit ",
+                            ISC_TRUE, qname, ISC_TRUE,
+                            DNS_RRL_RESULT_OK, resp_result,
+                            log_buf, log_buf_len);
+               if (!e->logged) {
+                       e->logged = ISC_TRUE;
+                       if (++rrl->num_logged <= 1)
+                               rrl->last_logged = e;
+               }
+               e->log_secs = 0;
+
+               /*
+                * Avoid holding the lock.
+                */
+               if (!wouldlog) {
+                       UNLOCK(&rrl->lock);
+                       e = NULL;
+               }
+               isc_log_write(dns_lctx, DNS_LOGCATEGORY_RRL,
+                             DNS_LOGMODULE_REQUEST, DNS_RRL_LOG_DROP,
+                             "%s", log_buf);
+       }
+
+       /*
+        * Make a log message for the caller.
+        */
+       if (wouldlog)
+               make_log_buf(rrl, e,
+                            rrl->log_only ? "would rate limit " : "rate limit ",
+                            NULL, ISC_FALSE, qname, ISC_FALSE,
+                            rrl_result, resp_result, log_buf, log_buf_len);
+
+       if (e != NULL) {
+               /*
+                * Do not save the qname unless we might need it for
+                * the ending log message.
+                */
+               if (!e->logged)
+                       free_qname(rrl, e);
+               UNLOCK(&rrl->lock);
+       }
+
+       return (rrl_result);
+}
+
+void
+dns_rrl_view_destroy(dns_view_t *view) {
+       dns_rrl_t *rrl;
+       dns_rrl_block_t *b;
+       dns_rrl_hash_t *h;
+       char log_buf[DNS_RRL_LOG_BUF_LEN];
+       int i;
+
+       rrl = view->rrl;
+       if (rrl == NULL)
+               return;
+       view->rrl = NULL;
+
+       /*
+        * Assume the caller takes care of locking the view and anything else.
+        */
+
+       if (rrl->num_logged > 0)
+               log_stops(rrl, 0, ISC_INT32_MAX, log_buf, sizeof(log_buf));
+
+       for (i = 0; i < DNS_RRL_QNAMES; ++i) {
+               if (rrl->qnames[i] == NULL)
+                       break;
+               isc_mem_put(rrl->mctx, rrl->qnames[i], sizeof(*rrl->qnames[i]));
+       }
+
+       if (rrl->exempt != NULL)
+               dns_acl_detach(&rrl->exempt);
+
+       DESTROYLOCK(&rrl->lock);
+
+       while (!ISC_LIST_EMPTY(rrl->blocks)) {
+               b = ISC_LIST_HEAD(rrl->blocks);
+               ISC_LIST_UNLINK(rrl->blocks, b, link);
+               isc_mem_put(rrl->mctx, b, b->size);
+       }
+
+       h = rrl->hash;
+       if (h != NULL)
+               isc_mem_put(rrl->mctx, h,
+                           sizeof(*h) + (h->length - 1) * sizeof(h->bins[0]));
+
+       h = rrl->old_hash;
+       if (h != NULL)
+               isc_mem_put(rrl->mctx, h,
+                           sizeof(*h) + (h->length - 1) * sizeof(h->bins[0]));
+
+       isc_mem_putanddetach(&rrl->mctx, rrl, sizeof(*rrl));
+}
+
+isc_result_t
+dns_rrl_init(dns_rrl_t **rrlp, dns_view_t *view, int min_entries) {
+       dns_rrl_t *rrl;
+       isc_result_t result;
+
+       *rrlp = NULL;
+
+       rrl = isc_mem_get(view->mctx, sizeof(*rrl));
+       if (rrl == NULL)
+               return (ISC_R_NOMEMORY);
+       memset(rrl, 0, sizeof(*rrl));
+       isc_mem_attach(view->mctx, &rrl->mctx);
+       result = isc_mutex_init(&rrl->lock);
+       if (result != ISC_R_SUCCESS) {
+               isc_mem_putanddetach(&rrl->mctx, rrl, sizeof(*rrl));
+               return (result);
+       }
+       isc_stdtime_get(&rrl->ts_bases[0]);
+
+       view->rrl = rrl;
+
+       result = expand_entries(rrl, min_entries);
+       if (result != ISC_R_SUCCESS) {
+               dns_rrl_view_destroy(view);
+               return (result);
+       }
+       result = expand_rrl_hash(rrl, 0);
+       if (result != ISC_R_SUCCESS) {
+               dns_rrl_view_destroy(view);
+               return (result);
+       }
+
+       *rrlp = rrl;
+       return (ISC_R_SUCCESS);
+}
index 021198fa70f641c9516cef748351151f867ae878..7daf64fae791f097b2547217c0c0260659c38412 100644 (file)
@@ -49,6 +49,7 @@
 #include <dns/masterdump.h>
 #include <dns/order.h>
 #include <dns/peer.h>
+#include <dns/rrl.h>
 #include <dns/rbt.h>
 #include <dns/rdataset.h>
 #include <dns/request.h>
@@ -184,6 +185,7 @@ dns_view_create(isc_mem_t *mctx, dns_rdataclass_t rdclass,
        view->answeracl_exclude = NULL;
        view->denyanswernames = NULL;
        view->answernames_exclude = NULL;
+       view->rrl = NULL;
        view->provideixfr = ISC_TRUE;
        view->maxcachettl = 7 * 24 * 3600;
        view->maxncachettl = 3 * 3600;
@@ -335,10 +337,16 @@ destroy(dns_view_t *view) {
                dns_acache_detach(&view->acache);
        }
        dns_rpz_view_destroy(view);
-#else
+#ifdef USE_RRL
+       dns_rrl_view_destroy(view);
+#else /* USE_RRL */
+       INSIST(view->rrl == NULL);
+#endif /* USE_RRL */
+#else /* BIND9 */
        INSIST(view->acache == NULL);
        INSIST(ISC_LIST_EMPTY(view->rpz_zones));
-#endif
+       INSIST(view->rrl == NULL);
+#endif /* BIND9 */
        if (view->requestmgr != NULL)
                dns_requestmgr_detach(&view->requestmgr);
        if (view->task != NULL)
index 3446d1d49e2660e22a6cab99c1dab34bbbd6cc97..4632763568b8978f84323c44cdfec33228cbbe55 100644 (file)
@@ -657,6 +657,9 @@ dns_rriterator_init
 dns_rriterator_next
 dns_rriterator_nextrrset
 dns_rriterator_pause
+dns_rrl
+dns_rrl_init
+dns_rrl_view_destroy
 dns_sdb_putnamedrr
 dns_sdb_putrdata
 dns_sdb_putrr
index a4120ac84f60e1c1963ded53fbba2471a42870bb..a9f8fdad50e004e1639b46ddfb728c93e14b268c 100644 (file)
@@ -346,6 +346,10 @@ SOURCE=..\include\dns\rpz.h
 # End Source File
 # Begin Source File
 
+SOURCE=..\include\dns\rrl.h
+# End Source File
+# Begin Source File
+
 SOURCE=..\include\dns\rriterator.h
 # End Source File
 # Begin Source File
@@ -650,6 +654,10 @@ SOURCE=..\rpz.c
 # End Source File
 # Begin Source File
 
+SOURCE=..\rrl.c
+# End Source File
+# Begin Source File
+
 SOURCE=..\rriterator.c
 # End Source File
 # Begin Source File
index ead7f828ff3280043702e71af7727e8b66b3254c..cbdf8585f0a1350d5561e63ab281e4aa7058a9ba 100644 (file)
@@ -184,6 +184,7 @@ CLEAN :
        -@erase "$(INTDIR)\result.obj"
        -@erase "$(INTDIR)\rootns.obj"
        -@erase "$(INTDIR)\rpz.obj"
+       -@erase "$(INTDIR)\rrl.obj"
        -@erase "$(INTDIR)\sdb.obj"
        -@erase "$(INTDIR)\sdlz.obj"
        -@erase "$(INTDIR)\soa.obj"
@@ -309,6 +310,7 @@ LINK32_OBJS= \
        "$(INTDIR)\result.obj" \
        "$(INTDIR)\rootns.obj" \
        "$(INTDIR)\rpz.obj" \
+       "$(INTDIR)\rrl.obj" \
        "$(INTDIR)\rriterator.obj" \
        "$(INTDIR)\sdb.obj" \
        "$(INTDIR)\sdlz.obj" \
@@ -505,6 +507,8 @@ CLEAN :
        -@erase "$(INTDIR)\rootns.sbr"
        -@erase "$(INTDIR)\rpz.obj"
        -@erase "$(INTDIR)\rpz.sbr"
+       -@erase "$(INTDIR)\rrl.obj"
+       -@erase "$(INTDIR)\rrl.sbr"
        -@erase "$(INTDIR)\rriterator.obj"
        -@erase "$(INTDIR)\rriterator.sbr"
        -@erase "$(INTDIR)\sdb.obj"
@@ -651,6 +655,7 @@ BSC32_SBRS= \
        "$(INTDIR)\result.sbr" \
        "$(INTDIR)\rootns.sbr" \
        "$(INTDIR)\rpz.sbr" \
+       "$(INTDIR)\rrl.sbr" \
        "$(INTDIR)\rriterator.sbr" \
        "$(INTDIR)\sdb.sbr" \
        "$(INTDIR)\sdlz.sbr" \
@@ -748,6 +753,7 @@ LINK32_OBJS= \
        "$(INTDIR)\result.obj" \
        "$(INTDIR)\rootns.obj" \
        "$(INTDIR)\rpz.obj" \
+       "$(INTDIR)\rrl.obj" \
        "$(INTDIR)\rriterator.obj" \
        "$(INTDIR)\sdb.obj" \
        "$(INTDIR)\sdlz.obj" \
@@ -1724,6 +1730,24 @@ SOURCE=..\rpz.c
        $(CPP) $(CPP_PROJ) $(SOURCE)
 
 
+!ENDIF 
+
+SOURCE=..\rrl.c
+
+!IF  "$(CFG)" == "libdns - Win32 Release"
+
+
+"$(INTDIR)\rrl.obj" : $(SOURCE) "$(INTDIR)"
+       $(CPP) $(CPP_PROJ) $(SOURCE)
+
+
+!ELSEIF  "$(CFG)" == "libdns - Win32 Debug"
+
+
+"$(INTDIR)\rrl.obj"    "$(INTDIR)\rrl.sbr" : $(SOURCE) "$(INTDIR)"
+       $(CPP) $(CPP_PROJ) $(SOURCE)
+
+
 !ENDIF 
 
 SOURCE=..\rriterator.c
index 431af746338b18378dcbe4ac6d69b8d3ed745c6b..15fd55499d3267ef1846614e8cfd9911a8a19ce3 100644 (file)
@@ -1269,6 +1269,39 @@ static cfg_type_t cfg_type_rpz = {
        rpz_fields
 };
 
+#ifdef USE_RRL
+/*
+ * rate-limit
+ */
+static cfg_clausedef_t rrl_clauses[] = {
+       { "responses-per-second", &cfg_type_uint32, 0 },
+       { "referrals-per-second", &cfg_type_uint32, 0 },
+       { "nodata-per-second", &cfg_type_uint32, 0 },
+       { "nxdomains-per-second", &cfg_type_uint32, 0 },
+       { "errors-per-second", &cfg_type_uint32, 0 },
+       { "all-per-second", &cfg_type_uint32, 0 },
+       { "slip", &cfg_type_uint32, 0 },
+       { "window", &cfg_type_uint32, 0 },
+       { "log-only", &cfg_type_boolean, 0 },
+       { "qps-scale", &cfg_type_uint32, 0 },
+       { "ipv4-prefix-length", &cfg_type_uint32, 0 },
+       { "ipv6-prefix-length", &cfg_type_uint32, 0 },
+       { "exempt-clients", &cfg_type_bracketed_aml, 0 },
+       { "max-table-size", &cfg_type_uint32, 0 },
+       { "min-table-size", &cfg_type_uint32, 0 },
+       { NULL, NULL, 0 }
+};
+
+static cfg_clausedef_t *rrl_clausesets[] = {
+       rrl_clauses,
+       NULL
+};
+
+static cfg_type_t cfg_type_rrl = {
+       "rate-limit", cfg_parse_map, cfg_print_map, cfg_doc_map,
+       &cfg_rep_map, rrl_clausesets
+};
+#endif /* USE_RRL */
 
 /*%
  * dnssec-lookaside
@@ -1423,6 +1456,9 @@ view_clauses[] = {
           CFG_CLAUSEFLAG_NOTCONFIGURED },
 #endif
        { "response-policy", &cfg_type_rpz, 0 },
+#ifdef USE_RRL
+       { "rate-limit", &cfg_type_rrl, 0 },
+#endif /* USE_RRL */
        { NULL, NULL, 0 }
 };