]> git.ipfire.org Git - thirdparty/unbound.git/commitdiff
- Fix for the DNSBomb vulnerability CVE-2024-33655. Thanks to Xiang Li
authorW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 1 May 2024 08:10:58 +0000 (10:10 +0200)
committerW.C.A. Wijngaards <wouter@nlnetlabs.nl>
Wed, 1 May 2024 08:10:58 +0000 (10:10 +0200)
  from the Network and Information Security Lab of Tsinghua University
  for reporting it.

20 files changed:
doc/Changelog
doc/example.conf.in
doc/unbound.conf.5.in
services/cache/infra.c
services/cache/infra.h
services/mesh.c
testdata/cachedb_expired_client_timeout.crpl
testdata/cachedb_subnet_expired.crpl
testdata/doh_downstream.tdir/doh_downstream.conf
testdata/doh_downstream_notls.tdir/doh_downstream_notls.conf
testdata/doh_downstream_post.tdir/doh_downstream_post.conf
testdata/fwd_three_service.tdir/fwd_three_service.conf
testdata/iter_ghost_timewindow.rpl
testdata/ssl_req_order.tdir/ssl_req_order.conf
testdata/tcp_req_order.tdir/tcp_req_order.conf
testdata/tcp_sigpipe.tdir/tcp_sigpipe.conf
util/config_file.c
util/config_file.h
util/configlexer.lex
util/configparser.y

index dab04633d4617023c81b5c97f85c815fbd23ed3d..24fbb42c7e251aa5e602d53845c06778e6ef07dd 100644 (file)
@@ -1,3 +1,8 @@
+1 May 2024: Wouter
+       - Fix for the DNSBomb vulnerability CVE-2024-33655. Thanks to Xiang Li
+         from the Network and Information Security Lab of Tsinghua University
+         for reporting it.
+
 29 April 2024: Yorgos
        - Cleanup unnecessary strdup calls for EDE strings.
 
index ea211e97d0f3fb2d24a0cffc2e7d94796736376e..e0aec8ec71e29327e4d06335009177d6381145e7 100644 (file)
@@ -191,6 +191,21 @@ server:
        # are behind a slow satellite link, to eg. 1128.
        # unknown-server-time-limit: 376
 
+       # msec before recursion replies are dropped. The work item continues.
+       # discard-timeout: 1900
+
+       # Max number of replies waiting for recursion per IP address.
+       # wait-limit: 1000
+
+       # Max replies waiting for recursion for IP address with cookie.
+       # wait-limit-cookie: 10000
+
+       # Apart from the default, the wait limit can be set for a netblock.
+       # wait-limit-netblock: 192.0.2.0/24 50000
+
+       # Apart from the default, the wait limit with cookie can be adjusted.
+       # wait-limit-cookie-netblock: 192.0.2.0/24 50000
+
        # the amount of memory to use for the RRset cache.
        # plain value in bytes or you can append k, m or G. default is "4Mb".
        # rrset-cache-size: 4m
index 167f4a5de6fe4e548d566c5b9bdeaac25bd2c63b..6d310c11639f023bb5d6864443842b80e8034e57 100644 (file)
@@ -302,6 +302,36 @@ Increase this if you are behind a slow satellite link, to eg. 1128.
 That would then avoid re\-querying every initial query because it times out.
 Default is 376 msec.
 .TP
+.B discard\-timeout: \fI<msec>
+The wait time in msec where recursion requests are dropped. This is
+to stop a large number of replies from accumulating. They receive
+no reply, the work item continues to recurse. It is nice to be a bit
+larger than serve\-expired\-client\-timeout if that is enabled.
+A value of 1900 msec is suggested. The value 0 disables it.
+Default 1900 msec.
+.TP
+.B wait\-limit: \fI<number>
+The number of replies that can wait for recursion, for an IP address.
+This makes a ratelimit per IP address of waiting replies for recursion.
+It stops very large amounts of queries waiting to be returned to one
+destination. The value 0 disables wait limits. Default is 1000.
+.TP
+.B wait\-limit\-cookie: \fI<number>
+The number of replies that can wait for recursion, for an IP address
+that sent the query with a valid DNS cookie. Since the cookie validates
+the client address, the limit can be higher. Default is 10000.
+.TP
+.B wait\-limit\-netblock: \fI<netblock> <number>
+The wait limit for the netblock. If not given the wait\-limit value is
+used. The most specific netblock is used to determine the limit. Useful for
+overriding the default for a specific, group or individual, server.
+The value -1 disables wait limits for the netblock.
+.TP
+.B wait\-limit\-cookie\-netblock: \fI<netblock> <number>
+The wait limit for the netblock, when the query has a DNS cookie.
+If not given, the wait\-limit\-cookie value is used.
+The value -1 disables wait limits for the netblock.
+.TP
 .B so\-rcvbuf: \fI<number>
 If not 0, then set the SO_RCVBUF socket option to get more buffer
 space on UDP port 53 incoming queries.  So that short spikes on busy
index 31462d13ae0a7eb77a25d2f17b1ebc2f3e04d010..457685ab59853688e27c0d843f4ea4e3d9ded1c3 100644 (file)
@@ -234,6 +234,81 @@ setup_domain_limits(struct infra_cache* infra, struct config_file* cfg)
        return 1;
 }
 
+/** find or create element in wait limit netblock tree */
+static struct wait_limit_netblock_info*
+wait_limit_netblock_findcreate(struct infra_cache* infra, char* str,
+       int cookie)
+{
+       rbtree_type* tree;
+       struct sockaddr_storage addr;
+       int net;
+       socklen_t addrlen;
+       struct wait_limit_netblock_info* d;
+
+       if(!netblockstrtoaddr(str, 0, &addr, &addrlen, &net)) {
+               log_err("cannot parse wait limit netblock '%s'", str);
+               return 0;
+       }
+
+       /* can we find it? */
+       if(cookie)
+               tree = &infra->wait_limits_cookie_netblock;
+       else
+               tree = &infra->wait_limits_netblock;
+       d = (struct wait_limit_netblock_info*)addr_tree_find(tree, &addr,
+               addrlen, net);
+       if(d)
+               return d;
+
+       /* create it */
+       d = (struct wait_limit_netblock_info*)calloc(1, sizeof(*d));
+       if(!d)
+               return NULL;
+       d->limit = -1;
+       if(!addr_tree_insert(tree, &d->node, &addr, addrlen, net)) {
+               log_err("duplicate element in domainlimit tree");
+               free(d);
+               return NULL;
+       }
+       return d;
+}
+
+
+/** insert wait limit information into lookup tree */
+static int
+infra_wait_limit_netblock_insert(struct infra_cache* infra,
+       struct config_file* cfg)
+{
+       struct config_str2list* p;
+       struct wait_limit_netblock_info* d;
+       for(p = cfg->wait_limit_netblock; p; p = p->next) {
+               d = wait_limit_netblock_findcreate(infra, p->str, 0);
+               if(!d)
+                       return 0;
+               d->limit = atoi(p->str2);
+       }
+       for(p = cfg->wait_limit_cookie_netblock; p; p = p->next) {
+               d = wait_limit_netblock_findcreate(infra, p->str, 1);
+               if(!d)
+                       return 0;
+               d->limit = atoi(p->str2);
+       }
+       return 1;
+}
+
+/** setup wait limits tree (0 on failure) */
+static int
+setup_wait_limits(struct infra_cache* infra, struct config_file* cfg)
+{
+       addr_tree_init(&infra->wait_limits_netblock);
+       addr_tree_init(&infra->wait_limits_cookie_netblock);
+       if(!infra_wait_limit_netblock_insert(infra, cfg))
+               return 0;
+       addr_tree_init_parents(&infra->wait_limits_netblock);
+       addr_tree_init_parents(&infra->wait_limits_cookie_netblock);
+       return 1;
+}
+
 struct infra_cache* 
 infra_create(struct config_file* cfg)
 {
@@ -267,6 +342,10 @@ infra_create(struct config_file* cfg)
                infra_delete(infra);
                return NULL;
        }
+       if(!setup_wait_limits(infra, cfg)) {
+               infra_delete(infra);
+               return NULL;
+       }
        infra_ip_ratelimit = cfg->ip_ratelimit;
        infra->client_ip_rates = slabhash_create(cfg->ip_ratelimit_slabs,
            INFRA_HOST_STARTSIZE, cfg->ip_ratelimit_size, &ip_rate_sizefunc,
@@ -287,6 +366,12 @@ static void domain_limit_free(rbnode_type* n, void* ATTR_UNUSED(arg))
        }
 }
 
+/** delete wait_limit_netblock_info entries */
+static void wait_limit_netblock_del(rbnode_type* n, void* ATTR_UNUSED(arg))
+{
+       free(n);
+}
+
 void 
 infra_delete(struct infra_cache* infra)
 {
@@ -296,6 +381,10 @@ infra_delete(struct infra_cache* infra)
        slabhash_delete(infra->domain_rates);
        traverse_postorder(&infra->domain_limits, domain_limit_free, NULL);
        slabhash_delete(infra->client_ip_rates);
+       traverse_postorder(&infra->wait_limits_netblock,
+               wait_limit_netblock_del, NULL);
+       traverse_postorder(&infra->wait_limits_cookie_netblock,
+               wait_limit_netblock_del, NULL);
        free(infra);
 }
 
@@ -880,7 +969,8 @@ static void infra_create_ratedata(struct infra_cache* infra,
 
 /** create rate data item for ip address */
 static void infra_ip_create_ratedata(struct infra_cache* infra,
-       struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow)
+       struct sockaddr_storage* addr, socklen_t addrlen, time_t timenow,
+       int mesh_wait)
 {
        hashvalue_type h = hash_addr(addr, addrlen, 0);
        struct ip_rate_key* k = (struct ip_rate_key*)calloc(1, sizeof(*k));
@@ -898,6 +988,7 @@ static void infra_ip_create_ratedata(struct infra_cache* infra,
        k->entry.data = d;
        d->qps[0] = 1;
        d->timestamp[0] = timenow;
+       d->mesh_wait = mesh_wait;
        slabhash_insert(infra->client_ip_rates, h, &k->entry, d, NULL);
 }
 
@@ -1121,6 +1212,81 @@ int infra_ip_ratelimit_inc(struct infra_cache* infra,
        }
 
        /* create */
-       infra_ip_create_ratedata(infra, addr, addrlen, timenow);
+       infra_ip_create_ratedata(infra, addr, addrlen, timenow, 0);
        return 1;
 }
+
+int infra_wait_limit_allowed(struct infra_cache* infra, struct comm_reply* rep,
+       int cookie_valid, struct config_file* cfg)
+{
+       struct lruhash_entry* entry;
+       if(cfg->wait_limit == 0)
+               return 1;
+
+       entry = infra_find_ip_ratedata(infra, &rep->client_addr,
+               rep->client_addrlen, 0);
+       if(entry) {
+               rbtree_type* tree;
+               struct wait_limit_netblock_info* w;
+               struct rate_data* d = (struct rate_data*)entry->data;
+               int mesh_wait = d->mesh_wait;
+               lock_rw_unlock(&entry->lock);
+
+               /* have the wait amount, check how much is allowed */
+               if(cookie_valid)
+                       tree = &infra->wait_limits_cookie_netblock;
+               else    tree = &infra->wait_limits_netblock;
+               w = (struct wait_limit_netblock_info*)addr_tree_lookup(tree,
+                       &rep->client_addr, rep->client_addrlen);
+               if(w) {
+                       if(w->limit != -1 && mesh_wait > w->limit)
+                               return 0;
+               } else {
+                       /* if there is no IP netblock specific information,
+                        * use the configured value. */
+                       if(mesh_wait > (cookie_valid?cfg->wait_limit_cookie:
+                               cfg->wait_limit))
+                               return 0;
+               }
+       }
+       return 1;
+}
+
+void infra_wait_limit_inc(struct infra_cache* infra, struct comm_reply* rep,
+       time_t timenow, struct config_file* cfg)
+{
+       struct lruhash_entry* entry;
+       if(cfg->wait_limit == 0)
+               return;
+
+       /* Find it */
+       entry = infra_find_ip_ratedata(infra, &rep->client_addr,
+               rep->client_addrlen, 1);
+       if(entry) {
+               struct rate_data* d = (struct rate_data*)entry->data;
+               d->mesh_wait++;
+               lock_rw_unlock(&entry->lock);
+               return;
+       }
+
+       /* Create it */
+       infra_ip_create_ratedata(infra, &rep->client_addr,
+               rep->client_addrlen, timenow, 1);
+}
+
+void infra_wait_limit_dec(struct infra_cache* infra, struct comm_reply* rep,
+       struct config_file* cfg)
+{
+       struct lruhash_entry* entry;
+       if(cfg->wait_limit == 0)
+               return;
+
+       entry = infra_find_ip_ratedata(infra, &rep->client_addr,
+               rep->client_addrlen, 1);
+       if(entry) {
+               struct rate_data* d = (struct rate_data*)entry->data;
+               if(d->mesh_wait > 0)
+                       d->mesh_wait--;
+               lock_rw_unlock(&entry->lock);
+       }
+}
index 525073bf35bb88e8544d46de4a90daa9aaaf7c75..ee6f384de3459431542c030701079b4fce47ec03 100644 (file)
@@ -122,6 +122,10 @@ struct infra_cache {
        rbtree_type domain_limits;
        /** hash table with query rates per client ip: ip_rate_key, ip_rate_data */
        struct slabhash* client_ip_rates;
+       /** tree of addr_tree_node, with wait_limit_netblock_info information */
+       rbtree_type wait_limits_netblock;
+       /** tree of addr_tree_node, with wait_limit_netblock_info information */
+       rbtree_type wait_limits_cookie_netblock;
 };
 
 /** ratelimit, unless overridden by domain_limits, 0 is off */
@@ -184,10 +188,22 @@ struct rate_data {
        /** what the timestamp is of the qps array members, counter is
         * valid for that timestamp.  Usually now and now-1. */
        time_t timestamp[RATE_WINDOW];
+       /** the number of queries waiting in the mesh */
+       int mesh_wait;
 };
 
 #define ip_rate_data rate_data
 
+/**
+ * Data to store the configuration per netblock for the wait limit
+ */
+struct wait_limit_netblock_info {
+       /** The addr tree node, this must be first. */
+       struct addr_tree_node node;
+       /** the limit on the amount */
+       int limit;
+};
+
 /** infra host cache default hash lookup size */
 #define INFRA_HOST_STARTSIZE 32
 /** bytes per zonename reserved in the hostcache, dnamelen(zonename.com.) */
@@ -474,4 +490,16 @@ void ip_rate_delkeyfunc(void* d, void* arg);
 /* delete data */
 #define ip_rate_deldatafunc rate_deldatafunc
 
+/** See if the IP address can have another reply in the wait limit */
+int infra_wait_limit_allowed(struct infra_cache* infra, struct comm_reply* rep,
+       int cookie_valid, struct config_file* cfg);
+
+/** Increment number of waiting replies for IP */
+void infra_wait_limit_inc(struct infra_cache* infra, struct comm_reply* rep,
+       time_t timenow, struct config_file* cfg);
+
+/** Decrement number of waiting replies for IP */
+void infra_wait_limit_dec(struct infra_cache* infra, struct comm_reply* rep,
+       struct config_file* cfg);
+
 #endif /* SERVICES_CACHE_INFRA_H */
index 55afd065f291a0aa3587f0d7262c676fac1daad0..e886c4b92c846ce4f686b99c5a3a8d1319e9993e 100644 (file)
@@ -47,6 +47,7 @@
 #include "services/outbound_list.h"
 #include "services/cache/dns.h"
 #include "services/cache/rrset.h"
+#include "services/cache/infra.h"
 #include "util/log.h"
 #include "util/net_help.h"
 #include "util/module.h"
@@ -415,6 +416,14 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo,
        if(rep->c->tcp_req_info) {
                r_buffer = rep->c->tcp_req_info->spool_buffer;
        }
+       if(!infra_wait_limit_allowed(mesh->env->infra_cache, rep,
+               edns->cookie_valid, mesh->env->cfg)) {
+               verbose(VERB_ALGO, "Too many queries waiting from the IP. "
+                       "dropping incoming query.");
+               comm_point_drop_reply(rep);
+               mesh->stats_dropped++;
+               return;
+       }
        if(!unique)
                s = mesh_area_find(mesh, cinfo, qinfo, qflags&(BIT_RD|BIT_CD), 0, 0);
        /* does this create a new reply state? */
@@ -522,6 +531,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo,
                }
        }
 #endif
+       infra_wait_limit_inc(mesh->env->infra_cache, rep, *mesh->env->now,
+               mesh->env->cfg);
        /* update statistics */
        if(was_detached) {
                log_assert(mesh->num_detached_states > 0);
@@ -953,6 +964,8 @@ mesh_state_cleanup(struct mesh_state* mstate)
                 * takes no time and also it does not do the mesh accounting */
                mstate->reply_list = NULL;
                for(; rep; rep=rep->next) {
+                       infra_wait_limit_dec(mesh->env->infra_cache,
+                               &rep->query_reply, mesh->env->cfg);
                        comm_point_drop_reply(&rep->query_reply);
                        log_assert(mesh->num_reply_addrs > 0);
                        mesh->num_reply_addrs--;
@@ -1436,6 +1449,8 @@ mesh_send_reply(struct mesh_state* m, int rcode, struct reply_info* rep,
                comm_point_send_reply(&r->query_reply);
                m->reply_list = rlist;
        }
+       infra_wait_limit_dec(m->s.env->infra_cache, &r->query_reply,
+               m->s.env->cfg);
        /* account */
        log_assert(m->s.env->mesh->num_reply_addrs > 0);
        m->s.env->mesh->num_reply_addrs--;
@@ -1491,6 +1506,28 @@ void mesh_query_done(struct mesh_state* mstate)
                }
        }
        for(r = mstate->reply_list; r; r = r->next) {
+               struct timeval old;
+               timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time);
+               if(mstate->s.env->cfg->discard_timeout != 0 &&
+                       ((int)old.tv_sec)*1000+((int)old.tv_usec)/1000 >
+                       mstate->s.env->cfg->discard_timeout) {
+                       /* Drop the reply, it is too old */
+                       /* briefly set the reply_list to NULL, so that the
+                        * tcp req info cleanup routine that calls the mesh
+                        * to deregister the meshstate for it is not done
+                        * because the list is NULL and also accounting is not
+                        * done there, but instead we do that here. */
+                       struct mesh_reply* reply_list = mstate->reply_list;
+                       verbose(VERB_ALGO, "drop reply, it is older than discard-timeout");
+                       infra_wait_limit_dec(mstate->s.env->infra_cache,
+                               &r->query_reply, mstate->s.env->cfg);
+                       mstate->reply_list = NULL;
+                       comm_point_drop_reply(&r->query_reply);
+                       mstate->reply_list = reply_list;
+                       mstate->s.env->mesh->stats_dropped++;
+                       continue;
+               }
+
                i++;
                tv = r->start_time;
 
@@ -1514,6 +1551,8 @@ void mesh_query_done(struct mesh_state* mstate)
                         * because the list is NULL and also accounting is not
                         * done there, but instead we do that here. */
                        struct mesh_reply* reply_list = mstate->reply_list;
+                       infra_wait_limit_dec(mstate->s.env->infra_cache,
+                               &r->query_reply, mstate->s.env->cfg);
                        mstate->reply_list = NULL;
                        comm_point_drop_reply(&r->query_reply);
                        mstate->reply_list = reply_list;
@@ -2046,6 +2085,8 @@ void mesh_state_remove_reply(struct mesh_area* mesh, struct mesh_state* m,
                        /* delete it, but allocated in m region */
                        log_assert(mesh->num_reply_addrs > 0);
                        mesh->num_reply_addrs--;
+                       infra_wait_limit_dec(mesh->env->infra_cache,
+                               &n->query_reply, mesh->env->cfg);
 
                        /* prev = prev; */
                        n = n->next;
@@ -2186,6 +2227,28 @@ mesh_serve_expired_callback(void* arg)
                log_dns_msg("Serve expired lookup", &qstate->qinfo, msg->rep);
 
        for(r = mstate->reply_list; r; r = r->next) {
+               struct timeval old;
+               timeval_subtract(&old, mstate->s.env->now_tv, &r->start_time);
+               if(mstate->s.env->cfg->discard_timeout != 0 &&
+                       ((int)old.tv_sec)*1000+((int)old.tv_usec)/1000 >
+                       mstate->s.env->cfg->discard_timeout) {
+                       /* Drop the reply, it is too old */
+                       /* briefly set the reply_list to NULL, so that the
+                        * tcp req info cleanup routine that calls the mesh
+                        * to deregister the meshstate for it is not done
+                        * because the list is NULL and also accounting is not
+                        * done there, but instead we do that here. */
+                       struct mesh_reply* reply_list = mstate->reply_list;
+                       verbose(VERB_ALGO, "drop reply, it is older than discard-timeout");
+                       infra_wait_limit_dec(mstate->s.env->infra_cache,
+                               &r->query_reply, mstate->s.env->cfg);
+                       mstate->reply_list = NULL;
+                       comm_point_drop_reply(&r->query_reply);
+                       mstate->reply_list = reply_list;
+                       mstate->s.env->mesh->stats_dropped++;
+                       continue;
+               }
+
                i++;
                tv = r->start_time;
 
@@ -2213,6 +2276,8 @@ mesh_serve_expired_callback(void* arg)
                        r, r_buffer, prev, prev_buffer);
                if(r->query_reply.c->tcp_req_info)
                        tcp_req_info_remove_mesh_state(r->query_reply.c->tcp_req_info, mstate);
+               infra_wait_limit_dec(mstate->s.env->infra_cache,
+                       &r->query_reply, mstate->s.env->cfg);
                prev = r;
                prev_buffer = r_buffer;
        }
index 23f6dbd79b51ed8a64bf83782110af1d6453b30d..78ddf4d8f698c019091b3a4efe37ddfe9efba667 100644 (file)
@@ -9,6 +9,7 @@ server:
        ; testbound script steps, but also reply within the time.
        serve-expired-client-timeout: 1200
        module-config: "cachedb iterator"
+       discard-timeout: 3000
 
 cachedb:
        backend: "testframe"
index a6057b0bba8c0ecb1482ca4083de82b4832de6d2..eddff1002dd8c0d282b0f9eb4e895c5540313c7a 100644 (file)
@@ -14,6 +14,7 @@ server:
        ; store for edns subnet content for modules to the right of it.
        ; this keeps subnet content out of cachedb as global content.
        module-config: "subnetcache cachedb iterator"
+       discard-timeout: 3000
 
 cachedb:
        backend: "testframe"
index f0857bb5851902d21ef7d8fcf5feede094629e1a..222c2159d27c088f2e7a3930a3b04372657dc708 100644 (file)
@@ -11,6 +11,7 @@ server:
        chroot: ""
        username: ""
        do-not-query-localhost: no
+       discard-timeout: 3000  # testns uses sleep=2
        http-query-buffer-size: 1G
        http-response-buffer-size: 1G
        http-max-streams: 200
index bdca456455ae6ff275d982d79e462633f129c042..161c35559f4f8cf68c44d08a47983eced4b28ff2 100644 (file)
@@ -11,6 +11,7 @@ server:
        chroot: ""
        username: ""
        do-not-query-localhost: no
+       discard-timeout: 3000  # testns uses sleep=2
        http-query-buffer-size: 1G
        http-response-buffer-size: 1G
        http-max-streams: 200
index f0857bb5851902d21ef7d8fcf5feede094629e1a..222c2159d27c088f2e7a3930a3b04372657dc708 100644 (file)
@@ -11,6 +11,7 @@ server:
        chroot: ""
        username: ""
        do-not-query-localhost: no
+       discard-timeout: 3000  # testns uses sleep=2
        http-query-buffer-size: 1G
        http-response-buffer-size: 1G
        http-max-streams: 200
index 05fafe015c492b65cb77c0d9768328b9328eb7f6..d6c9a205ffdcaa98564ccea19da5e29dc875c74e 100644 (file)
@@ -11,6 +11,7 @@ server:
        num-queries-per-thread: 1024
        use-syslog: no
        do-not-query-localhost: no
+       discard-timeout: 3000  # testns uses sleep=2
 forward-zone:
        name: "."
        forward-addr: "127.0.0.1@@TOPORT@"
index 566be82a9cf8622d63c794640704aa4a50e6bf2d..9e304628c98b6f068abc75a876e4fdb4ff53654a 100644 (file)
@@ -3,6 +3,7 @@ server:
        target-fetch-policy: "0 0 0 0 0"
        qname-minimisation: "no"
        minimal-responses: no
+       discard-timeout: 86400
 
 stub-zone:
        name: "."
index 3b2e2b1b4fa9701018a994e78cdffa6e58cbeaac..ec39d3ab28236aaf3152b955a454c1dda12ed01e 100644 (file)
@@ -9,6 +9,7 @@ server:
        chroot: ""
        username: ""
        do-not-query-localhost: no
+       discard-timeout: 3000  # testns uses sleep=2
        ssl-port: @PORT@
        ssl-service-key: "unbound_server.key"
        ssl-service-pem: "unbound_server.pem"
index 40d6f55c8cde4f63127f60fcd750206564e01a16..b2804e8e2d2dcabff9718faea3345c1b6900194e 100644 (file)
@@ -9,6 +9,7 @@ server:
        chroot: ""
        username: ""
        do-not-query-localhost: no
+       discard-timeout: 3000  # testns uses sleep=2
 
        local-zone: "example.net" static
        local-data: "www1.example.net. IN A 1.2.3.1"
index 384f16b0738a3acb74b3fa2ffac296cc37ace740..4f1ff9b088a89582317827ade12a7acfc6728956 100644 (file)
@@ -1,5 +1,5 @@
 server:
-       verbosity: 2
+       verbosity: 4
        # num-threads: 1
        interface: 127.0.0.1
        port: @PORT@
@@ -9,6 +9,7 @@ server:
        chroot: ""
        username: ""
        do-not-query-localhost: no
+       discard-timeout: 3000  # testns uses sleep=2
 
 forward-zone:
        name: "."
index e4fe3ee9fc6bda0bcee186559f920e9d6ea35862..2b67d4c19683fae8b52af364af515c4a1b775a07 100644 (file)
@@ -309,6 +309,11 @@ config_create(void)
        cfg->minimal_responses = 1;
        cfg->rrset_roundrobin = 1;
        cfg->unknown_server_time_limit = 376;
+       cfg->discard_timeout = 1900; /* msec */
+       cfg->wait_limit = 1000;
+       cfg->wait_limit_cookie = 10000;
+       cfg->wait_limit_netblock = NULL;
+       cfg->wait_limit_cookie_netblock = NULL;
        cfg->max_udp_size = 1232; /* value taken from edns_buffer_size */
        if(!(cfg->server_key_file = strdup(RUN_DIR"/unbound_server.key")))
                goto error_exit;
@@ -726,6 +731,9 @@ int config_set_option(struct config_file* cfg, const char* opt,
        else S_YNO("minimal-responses:", minimal_responses)
        else S_YNO("rrset-roundrobin:", rrset_roundrobin)
        else S_NUMBER_OR_ZERO("unknown-server-time-limit:", unknown_server_time_limit)
+       else S_NUMBER_OR_ZERO("discard-timeout:", discard_timeout)
+       else S_NUMBER_OR_ZERO("wait-limit:", wait_limit)
+       else S_NUMBER_OR_ZERO("wait-limit-cookie:", wait_limit_cookie)
        else S_STRLIST("local-data:", local_data)
        else S_YNO("unblock-lan-zones:", unblock_lan_zones)
        else S_YNO("insecure-lan-zones:", insecure_lan_zones)
@@ -1207,6 +1215,11 @@ config_get_option(struct config_file* cfg, const char* opt,
        else O_YNO(opt, "minimal-responses", minimal_responses)
        else O_YNO(opt, "rrset-roundrobin", rrset_roundrobin)
        else O_DEC(opt, "unknown-server-time-limit", unknown_server_time_limit)
+       else O_DEC(opt, "discard-timeout", discard_timeout)
+       else O_DEC(opt, "wait-limit", wait_limit)
+       else O_DEC(opt, "wait-limit-cookie", wait_limit_cookie)
+       else O_LS2(opt, "wait-limit-netblock", wait_limit_netblock)
+       else O_LS2(opt, "wait-limit-cookie-netblock", wait_limit_cookie_netblock)
 #ifdef CLIENT_SUBNET
        else O_LST(opt, "send-client-subnet", client_subnet)
        else O_LST(opt, "client-subnet-zone", client_subnet_zone)
@@ -1678,6 +1691,8 @@ config_delete(struct config_file* cfg)
        config_deltrplstrlist(cfg->interface_tag_actions);
        config_deltrplstrlist(cfg->interface_tag_datas);
        config_delstrlist(cfg->control_ifs.first);
+       config_deldblstrlist(cfg->wait_limit_netblock);
+       config_deldblstrlist(cfg->wait_limit_cookie_netblock);
        free(cfg->server_key_file);
        free(cfg->server_cert_file);
        free(cfg->control_key_file);
index 42836796e4bc6e45b387146f2607285d5db8418e..d3a2e268c49fe4f446c89dc34a43f96efa06db1e 100644 (file)
@@ -537,6 +537,21 @@ struct config_file {
        /* wait time for unknown server in msec */
        int unknown_server_time_limit;
 
+       /** Wait time to drop recursion replies */
+       int discard_timeout;
+
+       /** Wait limit for number of replies per IP address */
+       int wait_limit;
+
+       /** Wait limit for number of replies per IP address with cookie */
+       int wait_limit_cookie;
+
+       /** wait limit per netblock */
+       struct config_str2list* wait_limit_netblock;
+
+       /** wait limit with cookie per netblock */
+       struct config_str2list* wait_limit_cookie_netblock;
+
        /* maximum UDP response size */
        size_t max_udp_size;
 
index 51bf0660112fb49f5241b580a025d90d38e12762..7ae1b8c38f46ce597f303fd041cef0b87d35ef28 100644 (file)
@@ -464,6 +464,11 @@ domain-insecure{COLON}             { YDVAR(1, VAR_DOMAIN_INSECURE) }
 minimal-responses{COLON}       { YDVAR(1, VAR_MINIMAL_RESPONSES) }
 rrset-roundrobin{COLON}                { YDVAR(1, VAR_RRSET_ROUNDROBIN) }
 unknown-server-time-limit{COLON} { YDVAR(1, VAR_UNKNOWN_SERVER_TIME_LIMIT) }
+discard-timeout{COLON}         { YDVAR(1, VAR_DISCARD_TIMEOUT) }
+wait-limit{COLON}              { YDVAR(1, VAR_WAIT_LIMIT) }
+wait-limit-cookie{COLON}       { YDVAR(1, VAR_WAIT_LIMIT_COOKIE) }
+wait-limit-netblock{COLON}     { YDVAR(1, VAR_WAIT_LIMIT_NETBLOCK) }
+wait-limit-cookie-netblock{COLON} { YDVAR(1, VAR_WAIT_LIMIT_COOKIE_NETBLOCK) }
 max-udp-size{COLON}            { YDVAR(1, VAR_MAX_UDP_SIZE) }
 dns64-prefix{COLON}            { YDVAR(1, VAR_DNS64_PREFIX) }
 dns64-synthall{COLON}          { YDVAR(1, VAR_DNS64_SYNTHALL) }
index 2adbf92cccea4b4f90b274ff2ad0701423d33ec3..0feeb61b168b70d52e638fd402b8676fbffa3bf4 100644 (file)
@@ -189,6 +189,8 @@ extern struct config_parser_state* cfg_parser;
 %token VAR_ANSWER_COOKIE VAR_COOKIE_SECRET VAR_IP_RATELIMIT_COOKIE
 %token VAR_FORWARD_NO_CACHE VAR_STUB_NO_CACHE VAR_LOG_SERVFAIL VAR_DENY_ANY
 %token VAR_UNKNOWN_SERVER_TIME_LIMIT VAR_LOG_TAG_QUERYREPLY
+%token VAR_DISCARD_TIMEOUT VAR_WAIT_LIMIT VAR_WAIT_LIMIT_COOKIE
+%token VAR_WAIT_LIMIT_NETBLOCK VAR_WAIT_LIMIT_COOKIE_NETBLOCK
 %token VAR_STREAM_WAIT_SIZE VAR_TLS_CIPHERS VAR_TLS_CIPHERSUITES VAR_TLS_USE_SNI
 %token VAR_IPSET VAR_IPSET_NAME_V4 VAR_IPSET_NAME_V6
 %token VAR_TLS_SESSION_TICKET_KEYS VAR_RPZ VAR_TAGS VAR_RPZ_ACTION_OVERRIDE
@@ -327,6 +329,8 @@ content_server: server_num_threads | server_verbosity | server_port |
        server_fast_server_permil | server_fast_server_num  | server_tls_win_cert |
        server_tcp_connection_limit | server_log_servfail | server_deny_any |
        server_unknown_server_time_limit | server_log_tag_queryreply |
+       server_discard_timeout | server_wait_limit | server_wait_limit_cookie |
+       server_wait_limit_netblock | server_wait_limit_cookie_netblock |
        server_stream_wait_size | server_tls_ciphers |
        server_tls_ciphersuites | server_tls_session_ticket_keys |
        server_answer_cookie | server_cookie_secret | server_ip_ratelimit_cookie |
@@ -2377,6 +2381,57 @@ server_unknown_server_time_limit: VAR_UNKNOWN_SERVER_TIME_LIMIT STRING_ARG
                free($2);
        }
        ;
+server_discard_timeout: VAR_DISCARD_TIMEOUT STRING_ARG
+       {
+               OUTYY(("P(server_discard_timeout:%s)\n", $2));
+               cfg_parser->cfg->discard_timeout = atoi($2);
+               free($2);
+       }
+       ;
+server_wait_limit: VAR_WAIT_LIMIT STRING_ARG
+       {
+               OUTYY(("P(server_wait_limit:%s)\n", $2));
+               cfg_parser->cfg->wait_limit = atoi($2);
+               free($2);
+       }
+       ;
+server_wait_limit_cookie: VAR_WAIT_LIMIT_COOKIE STRING_ARG
+       {
+               OUTYY(("P(server_wait_limit_cookie:%s)\n", $2));
+               cfg_parser->cfg->wait_limit_cookie = atoi($2);
+               free($2);
+       }
+       ;
+server_wait_limit_netblock: VAR_WAIT_LIMIT_NETBLOCK STRING_ARG STRING_ARG
+       {
+               OUTYY(("P(server_wait_limit_netblock:%s %s)\n", $2, $3));
+               if(atoi($3) == 0 && strcmp($3, "0") != 0) {
+                       yyerror("number expected");
+                       free($2);
+                       free($3);
+               } else {
+                       if(!cfg_str2list_insert(&cfg_parser->cfg->
+                               wait_limit_netblock, $2, $3))
+                               fatal_exit("out of memory adding "
+                                       "wait-limit-netblock");
+               }
+       }
+       ;
+server_wait_limit_cookie_netblock: VAR_WAIT_LIMIT_COOKIE_NETBLOCK STRING_ARG STRING_ARG
+       {
+               OUTYY(("P(server_wait_limit_cookie_netblock:%s %s)\n", $2, $3));
+               if(atoi($3) == 0 && strcmp($3, "0") != 0) {
+                       yyerror("number expected");
+                       free($2);
+                       free($3);
+               } else {
+                       if(!cfg_str2list_insert(&cfg_parser->cfg->
+                               wait_limit_cookie_netblock, $2, $3))
+                               fatal_exit("out of memory adding "
+                                       "wait-limit-cookie-netblock");
+               }
+       }
+       ;
 server_max_udp_size: VAR_MAX_UDP_SIZE STRING_ARG
        {
                OUTYY(("P(server_max_udp_size:%s)\n", $2));