diff --git a/doc/example.conf.in b/doc/example.conf.in index 1ac155b7c..670479808 100644 --- a/doc/example.conf.in +++ b/doc/example.conf.in @@ -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 diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index 84eddd941..cc5fd64d9 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -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 +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 +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 +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 +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 +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 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 diff --git a/services/cache/infra.c b/services/cache/infra.c index 31462d13a..457685ab5 100644 --- a/services/cache/infra.c +++ b/services/cache/infra.c @@ -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); + } +} diff --git a/services/cache/infra.h b/services/cache/infra.h index 525073bf3..ee6f384de 100644 --- a/services/cache/infra.h +++ b/services/cache/infra.h @@ -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 */ diff --git a/services/mesh.c b/services/mesh.c index 47cfb0424..2b06957de 100644 --- a/services/mesh.c +++ b/services/mesh.c @@ -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? */ @@ -511,6 +520,8 @@ void mesh_new_client(struct mesh_area* mesh, struct query_info* qinfo, log_err("mesh_new_client: out of memory initializing serve expired"); goto servfail_mem; } + 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); @@ -930,6 +941,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--; @@ -1413,6 +1426,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--; @@ -1470,6 +1485,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; @@ -1493,6 +1530,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; @@ -2025,6 +2064,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; @@ -2165,6 +2206,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; @@ -2192,6 +2255,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; } diff --git a/testdata/doh_downstream.tdir/doh_downstream.conf b/testdata/doh_downstream.tdir/doh_downstream.conf index f0857bb58..222c2159d 100644 --- a/testdata/doh_downstream.tdir/doh_downstream.conf +++ b/testdata/doh_downstream.tdir/doh_downstream.conf @@ -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 diff --git a/testdata/doh_downstream_notls.tdir/doh_downstream_notls.conf b/testdata/doh_downstream_notls.tdir/doh_downstream_notls.conf index bdca45645..161c35559 100644 --- a/testdata/doh_downstream_notls.tdir/doh_downstream_notls.conf +++ b/testdata/doh_downstream_notls.tdir/doh_downstream_notls.conf @@ -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 diff --git a/testdata/doh_downstream_post.tdir/doh_downstream_post.conf b/testdata/doh_downstream_post.tdir/doh_downstream_post.conf index f0857bb58..222c2159d 100644 --- a/testdata/doh_downstream_post.tdir/doh_downstream_post.conf +++ b/testdata/doh_downstream_post.tdir/doh_downstream_post.conf @@ -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 diff --git a/testdata/fwd_three_service.tdir/fwd_three_service.conf b/testdata/fwd_three_service.tdir/fwd_three_service.conf index 05fafe015..d6c9a205f 100644 --- a/testdata/fwd_three_service.tdir/fwd_three_service.conf +++ b/testdata/fwd_three_service.tdir/fwd_three_service.conf @@ -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@" diff --git a/testdata/iter_ghost_timewindow.rpl b/testdata/iter_ghost_timewindow.rpl index 566be82a9..9e304628c 100644 --- a/testdata/iter_ghost_timewindow.rpl +++ b/testdata/iter_ghost_timewindow.rpl @@ -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: "." diff --git a/testdata/ssl_req_order.tdir/ssl_req_order.conf b/testdata/ssl_req_order.tdir/ssl_req_order.conf index 3b2e2b1b4..ec39d3ab2 100644 --- a/testdata/ssl_req_order.tdir/ssl_req_order.conf +++ b/testdata/ssl_req_order.tdir/ssl_req_order.conf @@ -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" diff --git a/testdata/tcp_req_order.tdir/tcp_req_order.conf b/testdata/tcp_req_order.tdir/tcp_req_order.conf index 40d6f55c8..b2804e8e2 100644 --- a/testdata/tcp_req_order.tdir/tcp_req_order.conf +++ b/testdata/tcp_req_order.tdir/tcp_req_order.conf @@ -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" diff --git a/testdata/tcp_sigpipe.tdir/tcp_sigpipe.conf b/testdata/tcp_sigpipe.tdir/tcp_sigpipe.conf index 384f16b07..4f1ff9b08 100644 --- a/testdata/tcp_sigpipe.tdir/tcp_sigpipe.conf +++ b/testdata/tcp_sigpipe.tdir/tcp_sigpipe.conf @@ -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: "." diff --git a/util/config_file.c b/util/config_file.c index 26185da02..147f41e85 100644 --- a/util/config_file.c +++ b/util/config_file.c @@ -308,6 +308,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; @@ -722,6 +727,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) @@ -1201,6 +1209,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) @@ -1671,6 +1684,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); diff --git a/util/config_file.h b/util/config_file.h index 491109833..7ded3c245 100644 --- a/util/config_file.h +++ b/util/config_file.h @@ -535,6 +535,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; diff --git a/util/configlexer.lex b/util/configlexer.lex index e1ab76e25..7455f50c0 100644 --- a/util/configlexer.lex +++ b/util/configlexer.lex @@ -463,6 +463,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) } diff --git a/util/configparser.y b/util/configparser.y index 0e4cd5960..7d95690ee 100644 --- a/util/configparser.y +++ b/util/configparser.y @@ -188,6 +188,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 @@ -325,6 +327,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 | @@ -2366,6 +2370,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));