From: George Thessalonikefs Date: Sat, 29 Jan 2022 22:49:38 +0000 (+0100) Subject: - Update ratelimit code for recent serviced_query changes and more X-Git-Tag: release-1.15.0rc1~12^2~4 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=f857af873ef078710caff566fc9879304f798716;p=thirdparty%2Funbound.git - Update ratelimit code for recent serviced_query changes and more accurate ratelimit calculation. --- diff --git a/daemon/worker.c b/daemon/worker.c index 5d2483cd2..a6b161f7a 100644 --- a/daemon/worker.c +++ b/daemon/worker.c @@ -1967,9 +1967,10 @@ worker_delete(struct worker* worker) struct outbound_entry* worker_send_query(struct query_info* qinfo, uint16_t flags, int dnssec, - int want_dnssec, int nocaps, struct sockaddr_storage* addr, - socklen_t addrlen, uint8_t* zone, size_t zonelen, int tcp_upstream, - int ssl_upstream, char* tls_auth_name, struct module_qstate* q) + int want_dnssec, int nocaps, int check_ratelimit, + struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, + size_t zonelen, int tcp_upstream, int ssl_upstream, char* tls_auth_name, + struct module_qstate* q, int* was_ratelimited) { struct worker* worker = q->env->worker; struct outbound_entry* e = (struct outbound_entry*)regional_alloc( @@ -1978,9 +1979,10 @@ worker_send_query(struct query_info* qinfo, uint16_t flags, int dnssec, return NULL; e->qstate = q; e->qsent = outnet_serviced_query(worker->back, qinfo, flags, dnssec, - want_dnssec, nocaps, tcp_upstream, + want_dnssec, nocaps, check_ratelimit, tcp_upstream, ssl_upstream, tls_auth_name, addr, addrlen, zone, zonelen, q, - worker_handle_service_reply, e, worker->back->udp_buff, q->env); + worker_handle_service_reply, e, worker->back->udp_buff, q->env, + was_ratelimited); if(!e->qsent) { return NULL; } @@ -2024,10 +2026,11 @@ struct outbound_entry* libworker_send_query( struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), int ATTR_UNUSED(nocaps), + int ATTR_UNUSED(check_ratelimit), struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), - struct module_qstate* ATTR_UNUSED(q)) + struct module_qstate* ATTR_UNUSED(q), int* ATTR_UNUSED(was_ratelimited)) { log_assert(0); return 0; diff --git a/dnstap/unbound-dnstap-socket.c b/dnstap/unbound-dnstap-socket.c index 990b8a866..63292fbca 100644 --- a/dnstap/unbound-dnstap-socket.c +++ b/dnstap/unbound-dnstap-socket.c @@ -1413,11 +1413,12 @@ void worker_sighandler(int ATTR_UNUSED(sig), void* ATTR_UNUSED(arg)) struct outbound_entry* worker_send_query( struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), - int ATTR_UNUSED(nocaps), struct sockaddr_storage* ATTR_UNUSED(addr), + int ATTR_UNUSED(nocaps), int ATTR_UNUSED(check_ratelimit), + struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), - struct module_qstate* ATTR_UNUSED(q)) + struct module_qstate* ATTR_UNUSED(q), int* ATTR_UNUSED(was_ratelimited)) { log_assert(0); return 0; @@ -1446,11 +1447,12 @@ worker_alloc_cleanup(void* ATTR_UNUSED(arg)) struct outbound_entry* libworker_send_query( struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), - int ATTR_UNUSED(nocaps), struct sockaddr_storage* ATTR_UNUSED(addr), + int ATTR_UNUSED(nocaps), int ATTR_UNUSED(check_ratelimit), + struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), - struct module_qstate* ATTR_UNUSED(q)) + struct module_qstate* ATTR_UNUSED(q), int* ATTR_UNUSED(was_ratelimited)) { log_assert(0); return 0; diff --git a/doc/unbound.conf.5.in b/doc/unbound.conf.5.in index c0878dad1..a4d7ca3bc 100644 --- a/doc/unbound.conf.5.in +++ b/doc/unbound.conf.5.in @@ -1648,7 +1648,8 @@ ratelimited by this setting. The zone of the query is determined by examining the nameservers for it, the zone name is used to keep track of the rate. For example, 1000 may be a suitable value to stop the server from being overloaded with random names, and keeps Unbound from sending traffic to the -nameservers for those zones. +nameservers for those zones. Configured forwarders are excluded from +ratelimiting. .TP 5 .B ratelimit\-size: \fI Give the size of the data structure in which the current ongoing rates are diff --git a/iterator/iterator.c b/iterator/iterator.c index 69e7e53dd..81120c6b6 100644 --- a/iterator/iterator.c +++ b/iterator/iterator.c @@ -1533,36 +1533,6 @@ processInitRequest(struct module_qstate* qstate, struct iter_qstate* iq, if(!iq->ratelimit_ok && qstate->prefetch_leeway) iq->ratelimit_ok = 1; /* allow prefetches, this keeps otherwise valid data in the cache */ - if(!iq->ratelimit_ok && infra_ratelimit_exceeded( - qstate->env->infra_cache, iq->dp->name, - iq->dp->namelen, *qstate->env->now)) { - /* and increment the rate, so that the rate for time - * now will also exceed the rate, keeping cache fresh */ - (void)infra_ratelimit_inc(qstate->env->infra_cache, - iq->dp->name, iq->dp->namelen, - *qstate->env->now, &qstate->qinfo, - qstate->reply); - /* see if we are passed through with slip factor */ - if(qstate->env->cfg->ratelimit_factor != 0 && - ub_random_max(qstate->env->rnd, - qstate->env->cfg->ratelimit_factor) == 1) { - iq->ratelimit_ok = 1; - log_nametypeclass(VERB_ALGO, "ratelimit allowed through for " - "delegation point", iq->dp->name, - LDNS_RR_TYPE_NS, LDNS_RR_CLASS_IN); - } else { - lock_basic_lock(&ie->queries_ratelimit_lock); - ie->num_queries_ratelimited++; - lock_basic_unlock(&ie->queries_ratelimit_lock); - log_nametypeclass(VERB_ALGO, "ratelimit exceeded with " - "delegation point", iq->dp->name, - LDNS_RR_TYPE_NS, LDNS_RR_CLASS_IN); - qstate->was_ratelimited = 1; - errinf(qstate, "query was ratelimited"); - errinf_dname(qstate, "for zone", iq->dp->name); - return error_response(qstate, id, LDNS_RCODE_SERVFAIL); - } - } /* see if this dp not useless. * It is useless if: @@ -2211,9 +2181,11 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, int auth_fallback = 0; uint8_t* qout_orig = NULL; size_t qout_orig_len = 0; + int sq_check_ratelimit = 1; + int sq_was_ratelimited = 0; - /* NOTE: a request will encounter this state for each target it - * needs to send a query to. That is, at least one per referral, + /* NOTE: a request will encounter this state for each target it + * needs to send a query to. That is, at least one per referral, * more if some targets timeout or return throwaway answers. */ log_query_info(VERB_QUERY, "processQueryTargets:", &qstate->qinfo); @@ -2646,22 +2618,9 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, return 0; } - /* if not forwarding, check ratelimits per delegationpoint name */ - if(!(iq->chase_flags & BIT_RD) && !iq->ratelimit_ok) { - if(!infra_ratelimit_inc(qstate->env->infra_cache, iq->dp->name, - iq->dp->namelen, *qstate->env->now, &qstate->qinfo, - qstate->reply)) { - lock_basic_lock(&ie->queries_ratelimit_lock); - ie->num_queries_ratelimited++; - lock_basic_unlock(&ie->queries_ratelimit_lock); - verbose(VERB_ALGO, "query exceeded ratelimits"); - qstate->was_ratelimited = 1; - errinf_dname(qstate, "exceeded ratelimit for zone", - iq->dp->name); - return error_response(qstate, id, LDNS_RCODE_SERVFAIL); - } - } - + /* Do not check ratelimit for forwarding queries or if we already got a + * pass. */ + sq_check_ratelimit = (!(iq->chase_flags & BIT_RD) && !iq->ratelimit_ok); /* We have a valid target. */ if(verbosity >= VERB_QUERY) { log_query_info(VERB_QUERY, "sending query:", &iq->qinfo_out); @@ -2673,25 +2632,32 @@ processQueryTargets(struct module_qstate* qstate, struct iter_qstate* iq, } fptr_ok(fptr_whitelist_modenv_send_query(qstate->env->send_query)); outq = (*qstate->env->send_query)(&iq->qinfo_out, - iq->chase_flags | (iq->chase_to_rd?BIT_RD:0), + iq->chase_flags | (iq->chase_to_rd?BIT_RD:0), /* unset CD if to forwarder(RD set) and not dnssec retry * (blacklist nonempty) and no trust-anchors are configured * above the qname or on the first attempt when dnssec is on */ EDNS_DO| ((iq->chase_to_rd||(iq->chase_flags&BIT_RD)!=0)&& !qstate->blacklist&&(!iter_qname_indicates_dnssec(qstate->env, - &iq->qinfo_out)||target->attempts==1)?0:BIT_CD), + &iq->qinfo_out)||target->attempts==1)?0:BIT_CD), iq->dnssec_expected, iq->caps_fallback || is_caps_whitelisted( - ie, iq), &target->addr, target->addrlen, + ie, iq), sq_check_ratelimit, &target->addr, target->addrlen, iq->dp->name, iq->dp->namelen, (iq->dp->tcp_upstream || qstate->env->cfg->tcp_upstream), (iq->dp->ssl_upstream || qstate->env->cfg->ssl_upstream), - target->tls_auth_name, qstate); + target->tls_auth_name, qstate, &sq_was_ratelimited); if(!outq) { + if(sq_was_ratelimited) { + lock_basic_lock(&ie->queries_ratelimit_lock); + ie->num_queries_ratelimited++; + lock_basic_unlock(&ie->queries_ratelimit_lock); + verbose(VERB_ALGO, "query exceeded ratelimits"); + qstate->was_ratelimited = 1; + errinf_dname(qstate, "exceeded ratelimit for zone", + iq->dp->name); + return error_response(qstate, id, LDNS_RCODE_SERVFAIL); + } log_addr(VERB_QUERY, "error sending query to auth server", &target->addr, target->addrlen); - if(!(iq->chase_flags & BIT_RD) && !iq->ratelimit_ok) - infra_ratelimit_dec(qstate->env->infra_cache, iq->dp->name, - iq->dp->namelen, *qstate->env->now); if(qstate->env->cfg->qname_minimisation) iq->minimisation_state = SKIP_MINIMISE_STATE; return next_state(iq, QUERYTARGETS_STATE); @@ -2935,14 +2901,6 @@ processQueryResponse(struct module_qstate* qstate, struct iter_qstate* iq, * delegation point, and back to the QUERYTARGETS_STATE. */ verbose(VERB_DETAIL, "query response was REFERRAL"); - if(!(iq->chase_flags & BIT_RD) && !iq->ratelimit_ok) { - /* we have a referral, no ratelimit, we can send - * our queries to the given name */ - infra_ratelimit_dec(qstate->env->infra_cache, - iq->dp->name, iq->dp->namelen, - *qstate->env->now); - } - /* if hardened, only store referral if we asked for it */ if(!qstate->no_cache_store && (!qstate->env->cfg->harden_referral_path || diff --git a/iterator/iterator.h b/iterator/iterator.h index f09506f2e..8b840528d 100644 --- a/iterator/iterator.h +++ b/iterator/iterator.h @@ -80,7 +80,7 @@ struct rbtree_type; /** * number of labels from QNAME that are always send individually when using * QNAME minimisation, even when the number of labels of the QNAME is bigger - * tham MAX_MINIMISE_COUNT */ + * than MAX_MINIMISE_COUNT */ #define MINIMISE_ONE_LAB 4 #define MINIMISE_MULTIPLE_LABS (MAX_MINIMISE_COUNT - MINIMISE_ONE_LAB) /** at what query-sent-count to stop target fetch policy */ diff --git a/libunbound/libworker.c b/libunbound/libworker.c index 7f753435d..ab28dd54f 100644 --- a/libunbound/libworker.c +++ b/libunbound/libworker.c @@ -882,9 +882,10 @@ void libworker_alloc_cleanup(void* arg) struct outbound_entry* libworker_send_query(struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, int nocaps, + int check_ratelimit, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, int tcp_upstream, int ssl_upstream, char* tls_auth_name, - struct module_qstate* q) + struct module_qstate* q, int* was_ratelimited) { struct libworker* w = (struct libworker*)q->env->worker; struct outbound_entry* e = (struct outbound_entry*)regional_alloc( @@ -893,9 +894,10 @@ struct outbound_entry* libworker_send_query(struct query_info* qinfo, return NULL; e->qstate = q; e->qsent = outnet_serviced_query(w->back, qinfo, flags, dnssec, - want_dnssec, nocaps, tcp_upstream, ssl_upstream, + want_dnssec, nocaps, check_ratelimit, tcp_upstream, ssl_upstream, tls_auth_name, addr, addrlen, zone, zonelen, q, - libworker_handle_service_reply, e, w->back->udp_buff, q->env); + libworker_handle_service_reply, e, w->back->udp_buff, q->env, + was_ratelimited); if(!e->qsent) { return NULL; } @@ -976,10 +978,11 @@ void worker_sighandler(int ATTR_UNUSED(sig), void* ATTR_UNUSED(arg)) struct outbound_entry* worker_send_query(struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), int ATTR_UNUSED(nocaps), + int ATTR_UNUSED(check_ratelimit), struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), - struct module_qstate* ATTR_UNUSED(q)) + struct module_qstate* ATTR_UNUSED(q), int* ATTR_UNUSED(was_ratelimited)) { log_assert(0); return 0; diff --git a/libunbound/worker.h b/libunbound/worker.h index 974b66a30..0fa5bfa99 100644 --- a/libunbound/worker.h +++ b/libunbound/worker.h @@ -58,6 +58,7 @@ struct query_info; * @param dnssec: if set, EDNS record will have DO bit set. * @param want_dnssec: signatures needed. * @param nocaps: ignore capsforid(if in config), do not perturb qname. + * @param check_ratelimit: if set, will check ratelimit before sending out. * @param addr: where to. * @param addrlen: length of addr. * @param zone: delegation point name. @@ -67,14 +68,17 @@ struct query_info; * @param tls_auth_name: if ssl_upstream, use this name with TLS * authentication. * @param q: which query state to reactivate upon return. + * @param was_ratelimited: it will signal back if the query failed to pass the + * ratelimit check. * @return: false on failure (memory or socket related). no query was * sent. */ struct outbound_entry* libworker_send_query(struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, int nocaps, + int check_ratelimit, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, int tcp_upstream, int ssl_upstream, char* tls_auth_name, - struct module_qstate* q); + struct module_qstate* q, int* was_ratelimited); /** process incoming serviced query replies from the network */ int libworker_handle_service_reply(struct comm_point* c, void* arg, int error, @@ -110,6 +114,7 @@ void worker_sighandler(int sig, void* arg); * @param dnssec: if set, EDNS record will have DO bit set. * @param want_dnssec: signatures needed. * @param nocaps: ignore capsforid(if in config), do not perturb qname. + * @param check_ratelimit: if set, will check ratelimit before sending out. * @param addr: where to. * @param addrlen: length of addr. * @param zone: wireformat dname of the zone. @@ -119,14 +124,17 @@ void worker_sighandler(int sig, void* arg); * @param tls_auth_name: if ssl_upstream, use this name with TLS * authentication. * @param q: which query state to reactivate upon return. + * @param was_ratelimited: it will signal back if the query failed to pass the + * ratelimit check. * @return: false on failure (memory or socket related). no query was * sent. */ struct outbound_entry* worker_send_query(struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, int nocaps, + int check_ratelimit, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, int tcp_upstream, int ssl_upstream, char* tls_auth_name, - struct module_qstate* q); + struct module_qstate* q, int* was_ratelimited); /** * process control messages from the main thread. Frees the control diff --git a/pythonmod/interface.i b/pythonmod/interface.i index 03483abdf..1ca8686a7 100644 --- a/pythonmod/interface.i +++ b/pythonmod/interface.i @@ -712,9 +712,10 @@ struct module_env { /* --- services --- */ struct outbound_entry* (*send_query)(struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, int nocaps, + int check_ratelimit, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, int tcp_upstream, int ssl_upstream, - char* tls_auth_name, struct module_qstate* q); + char* tls_auth_name, struct module_qstate* q, int* was_ratelimited); void (*detach_subs)(struct module_qstate* qstate); int (*attach_sub)(struct module_qstate* qstate, struct query_info* qinfo, uint16_t qflags, int prime, diff --git a/services/cache/infra.c b/services/cache/infra.c index 518e69622..01e1a7cdd 100644 --- a/services/cache/infra.c +++ b/services/cache/infra.c @@ -898,8 +898,9 @@ static void infra_ip_create_ratedata(struct infra_cache* infra, slabhash_insert(infra->client_ip_rates, h, &k->entry, d, NULL); } -/** find the second and return its rate counter, if none, remove oldest */ -static int* infra_rate_find_second(void* data, time_t t) +/** Find the second and return its rate counter. If none and should_add, remove + * oldest to accommodate. Else return none. */ +static int* infra_rate_find_second_or_none(void* data, time_t t, int should_add) { struct rate_data* d = (struct rate_data*)data; int i, oldest; @@ -907,6 +908,7 @@ static int* infra_rate_find_second(void* data, time_t t) if(d->timestamp[i] == t) return &(d->qps[i]); } + if(!should_add) return NULL; /* remove oldest timestamp, and insert it at t with 0 qps */ oldest = 0; for(i=0; iqps[oldest]); } +/** find the second and return its rate counter, if none, remove oldest to + * accommodate */ +static int* infra_rate_give_second(void* data, time_t t) +{ + return infra_rate_find_second_or_none(data, t, 1); +} + +/** find the second and return its rate counter only if it exists. Caller + * should check for NULL return value */ +static int* infra_rate_get_second(void* data, time_t t) +{ + return infra_rate_find_second_or_none(data, t, 0); +} + int infra_rate_max(void* data, time_t now) { struct rate_data* d = (struct rate_data*)data; int i, max = 0; for(i=0; itimestamp[i] <= RATE_WINDOW) { - if(d->qps[i] > max) - max = d->qps[i]; + if(now == d->timestamp[i]) { + return d->qps[i]; } } return max; @@ -950,12 +965,12 @@ int infra_ratelimit_inc(struct infra_cache* infra, uint8_t* name, entry = infra_find_ratedata(infra, name, namelen, 1); if(entry) { int premax = infra_rate_max(entry->data, timenow); - int* cur = infra_rate_find_second(entry->data, timenow); + int* cur = infra_rate_give_second(entry->data, timenow); (*cur)++; max = infra_rate_max(entry->data, timenow); lock_rw_unlock(&entry->lock); - if(premax < lim && max >= lim) { + if(premax <= lim && max > lim) { char buf[257], qnm[257], ts[12], cs[12], ip[128]; dname_str(name, buf); dname_str(qinfo->qname, qnm); @@ -970,12 +985,12 @@ int infra_ratelimit_inc(struct infra_cache* infra, uint8_t* name, verbose(VERB_OPS, "ratelimit exceeded %s %d query %s %s %s", buf, lim, qnm, cs, ts); } } - return (max < lim); + return (max <= lim); } /* create */ infra_create_ratedata(infra, name, namelen, timenow); - return (1 < lim); + return (1 <= lim); } void infra_ratelimit_dec(struct infra_cache* infra, uint8_t* name, @@ -987,7 +1002,12 @@ void infra_ratelimit_dec(struct infra_cache* infra, uint8_t* name, return; /* not enabled */ entry = infra_find_ratedata(infra, name, namelen, 1); if(!entry) return; /* not cached */ - cur = infra_rate_find_second(entry->data, timenow); + cur = infra_rate_get_second(entry->data, timenow); + if(cur == NULL) { + /* our timenow is not available anymore; nothing to decrease */ + lock_rw_unlock(&entry->lock); + return; + } if((*cur) > 0) (*cur)--; lock_rw_unlock(&entry->lock); @@ -1027,7 +1047,7 @@ infra_get_mem(struct infra_cache* infra) } int infra_ip_ratelimit_inc(struct infra_cache* infra, - struct comm_reply* repinfo, time_t timenow, struct sldns_buffer* buffer) + struct comm_reply* repinfo, time_t timenow, struct sldns_buffer* buffer) { int max; struct lruhash_entry* entry; @@ -1040,7 +1060,7 @@ int infra_ip_ratelimit_inc(struct infra_cache* infra, entry = infra_find_ip_ratedata(infra, repinfo, 1); if(entry) { int premax = infra_rate_max(entry->data, timenow); - int* cur = infra_rate_find_second(entry->data, timenow); + int* cur = infra_rate_give_second(entry->data, timenow); (*cur)++; max = infra_rate_max(entry->data, timenow); lock_rw_unlock(&entry->lock); diff --git a/services/cache/infra.h b/services/cache/infra.h index 14f97c4c6..a10e70f7d 100644 --- a/services/cache/infra.h +++ b/services/cache/infra.h @@ -403,7 +403,7 @@ void infra_ratelimit_dec(struct infra_cache* infra, uint8_t* name, int infra_ratelimit_exceeded(struct infra_cache* infra, uint8_t* name, size_t namelen, time_t timenow); -/** find the maximum rate stored, not too old. 0 if no information. */ +/** find the maximum rate stored. 0 if no information. */ int infra_rate_max(void* data, time_t now); /** find the ratelimit in qps for a domain. 0 if no limit for domain. */ diff --git a/services/outside_network.c b/services/outside_network.c index 651f221fd..d0a53fe4b 100644 --- a/services/outside_network.c +++ b/services/outside_network.c @@ -3333,11 +3333,11 @@ serviced_udp_callback(struct comm_point* c, void* arg, int error, struct serviced_query* outnet_serviced_query(struct outside_network* outnet, struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, - int nocaps, int tcp_upstream, int ssl_upstream, char* tls_auth_name, - struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, - size_t zonelen, struct module_qstate* qstate, + int nocaps, int check_ratelimit, int tcp_upstream, int ssl_upstream, + char* tls_auth_name, struct sockaddr_storage* addr, socklen_t addrlen, + uint8_t* zone, size_t zonelen, struct module_qstate* qstate, comm_point_callback_type* callback, void* callback_arg, - sldns_buffer* buff, struct module_env* env) + sldns_buffer* buff, struct module_env* env, int* was_ratelimited) { struct serviced_query* sq; struct service_callback* cb; @@ -3345,6 +3345,7 @@ outnet_serviced_query(struct outside_network* outnet, struct regional* region; struct edns_option* backed_up_opt_list = qstate->edns_opts_back_out; struct edns_option* per_upstream_opt_list = NULL; + time_t timenow = 0; /* If we have an already populated EDNS option list make a copy since * we may now add upstream specific EDNS options. */ @@ -3386,6 +3387,26 @@ outnet_serviced_query(struct outside_network* outnet, sq = lookup_serviced(outnet, buff, dnssec, addr, addrlen, per_upstream_opt_list); if(!sq) { + /* Check ratelimit only for new serviced_query */ + if(check_ratelimit) { + timenow = *env->now; + if(!infra_ratelimit_inc(env->infra_cache, zone, + zonelen, timenow, &qstate->qinfo, + qstate->reply)) { + /* Can we pass through with slip factor? */ + if(env->cfg->ratelimit_factor == 0 || + ub_random_max(env->rnd, + env->cfg->ratelimit_factor) != 1) { + *was_ratelimited = 1; + alloc_reg_release(env->alloc, region); + return NULL; + } + log_nametypeclass(VERB_ALGO, + "ratelimit allowed through for " + "delegation point", zone, + LDNS_RR_TYPE_NS, LDNS_RR_CLASS_IN); + } + } /* make new serviced query entry */ sq = serviced_create(outnet, buff, dnssec, want_dnssec, nocaps, tcp_upstream, ssl_upstream, tls_auth_name, addr, @@ -3395,11 +3416,19 @@ outnet_serviced_query(struct outside_network* outnet, ? env->cfg->pad_queries_block_size : 0 ), env->alloc, region); if(!sq) { + if(check_ratelimit) { + infra_ratelimit_dec(env->infra_cache, + zone, zonelen, timenow); + } alloc_reg_release(env->alloc, region); return NULL; } if(!(cb = (struct service_callback*)regional_alloc( sq->region, sizeof(*cb)))) { + if(check_ratelimit) { + infra_ratelimit_dec(env->infra_cache, + zone, zonelen, timenow); + } (void)rbtree_delete(outnet->serviced, sq); serviced_node_del(&sq->node, NULL); return NULL; diff --git a/services/outside_network.h b/services/outside_network.h index 8f31d43cc..4c5b96f83 100644 --- a/services/outside_network.h +++ b/services/outside_network.h @@ -632,6 +632,7 @@ void pending_delete(struct outside_network* outnet, struct pending* p); * @param want_dnssec: signatures are needed, without EDNS the answer is * likely to be useless. * @param nocaps: ignore use_caps_for_id and use unperturbed qname. + * @param check_ratelimit: if set, will check ratelimit before sending out. * @param tcp_upstream: use TCP for upstream queries. * @param ssl_upstream: use SSL for upstream queries. * @param tls_auth_name: when ssl_upstream is true, use this name to check @@ -648,16 +649,18 @@ void pending_delete(struct outside_network* outnet, struct pending* p); * @param callback_arg: user argument to callback function. * @param buff: scratch buffer to create query contents in. Empty on exit. * @param env: the module environment. + * @param was_ratelimited: it will signal back if the query failed to pass the + * ratelimit check. * @return 0 on error, or pointer to serviced query that is used to answer * this serviced query may be shared with other callbacks as well. */ struct serviced_query* outnet_serviced_query(struct outside_network* outnet, struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, - int nocaps, int tcp_upstream, int ssl_upstream, char* tls_auth_name, - struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, - size_t zonelen, struct module_qstate* qstate, + int nocaps, int check_ratelimit, int tcp_upstream, int ssl_upstream, + char* tls_auth_name, struct sockaddr_storage* addr, socklen_t addrlen, + uint8_t* zone, size_t zonelen, struct module_qstate* qstate, comm_point_callback_type* callback, void* callback_arg, - struct sldns_buffer* buff, struct module_env* env); + struct sldns_buffer* buff, struct module_env* env, int* was_ratelimited); /** * Remove service query callback. diff --git a/smallapp/worker_cb.c b/smallapp/worker_cb.c index 78e773938..c68981735 100644 --- a/smallapp/worker_cb.c +++ b/smallapp/worker_cb.c @@ -97,10 +97,12 @@ void worker_sighandler(int ATTR_UNUSED(sig), void* ATTR_UNUSED(arg)) struct outbound_entry* worker_send_query( struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), - int ATTR_UNUSED(nocaps), struct sockaddr_storage* ATTR_UNUSED(addr), + int ATTR_UNUSED(nocaps), int ATTR_UNUSED(check_ratelimit), + struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), int ATTR_UNUSED(ssl_upstream), - char* ATTR_UNUSED(tls_auth_name), struct module_qstate* ATTR_UNUSED(q)) + char* ATTR_UNUSED(tls_auth_name), struct module_qstate* ATTR_UNUSED(q), + int* ATTR_UNUSED(was_ratelimited)) { log_assert(0); return 0; @@ -129,10 +131,12 @@ worker_alloc_cleanup(void* ATTR_UNUSED(arg)) struct outbound_entry* libworker_send_query( struct query_info* ATTR_UNUSED(qinfo), uint16_t ATTR_UNUSED(flags), int ATTR_UNUSED(dnssec), int ATTR_UNUSED(want_dnssec), - int ATTR_UNUSED(nocaps), struct sockaddr_storage* ATTR_UNUSED(addr), + int ATTR_UNUSED(nocaps), int ATTR_UNUSED(check_ratelimit), + struct sockaddr_storage* ATTR_UNUSED(addr), socklen_t ATTR_UNUSED(addrlen), uint8_t* ATTR_UNUSED(zone), size_t ATTR_UNUSED(zonelen), int ATTR_UNUSED(tcp_upstream), int ATTR_UNUSED(ssl_upstream), - char* ATTR_UNUSED(tls_auth_name), struct module_qstate* ATTR_UNUSED(q)) + char* ATTR_UNUSED(tls_auth_name), struct module_qstate* ATTR_UNUSED(q), + int* ATTR_UNUSED(was_ratelimited)) { log_assert(0); return 0; diff --git a/testcode/fake_event.c b/testcode/fake_event.c index 741c035f8..be06a4721 100644 --- a/testcode/fake_event.c +++ b/testcode/fake_event.c @@ -1187,12 +1187,13 @@ pending_tcp_query(struct serviced_query* sq, sldns_buffer* packet, struct serviced_query* outnet_serviced_query(struct outside_network* outnet, struct query_info* qinfo, uint16_t flags, int dnssec, int ATTR_UNUSED(want_dnssec), int ATTR_UNUSED(nocaps), + int ATTR_UNUSED(check_ratelimit), int ATTR_UNUSED(tcp_upstream), int ATTR_UNUSED(ssl_upstream), char* ATTR_UNUSED(tls_auth_name), struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, struct module_qstate* qstate, comm_point_callback_type* callback, void* callback_arg, sldns_buffer* ATTR_UNUSED(buff), - struct module_env* env) + struct module_env* env, int* ATTR_UNUSED(was_ratelimited)) { struct replay_runtime* runtime = (struct replay_runtime*)outnet->base; struct fake_pending* pend = (struct fake_pending*)calloc(1, diff --git a/util/fptr_wlist.c b/util/fptr_wlist.c index 83b32a004..05a22d402 100644 --- a/util/fptr_wlist.c +++ b/util/fptr_wlist.c @@ -335,9 +335,10 @@ fptr_whitelist_hash_markdelfunc(lruhash_markdelfunc_type fptr) int fptr_whitelist_modenv_send_query(struct outbound_entry* (*fptr)( struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, - int nocaps, struct sockaddr_storage* addr, socklen_t addrlen, - uint8_t* zone, size_t zonelen, int tcp_upstream, int ssl_upstream, char* tls_auth_name, - struct module_qstate* q)) + int nocaps, int check_ratelimit, struct sockaddr_storage* addr, + socklen_t addrlen, uint8_t* zone, size_t zonelen, int tcp_upstream, + int ssl_upstream, char* tls_auth_name, struct module_qstate* q, + int* was_ratelimited)) { if(fptr == &worker_send_query) return 1; else if(fptr == &libworker_send_query) return 1; diff --git a/util/fptr_wlist.h b/util/fptr_wlist.h index a54709925..a0d986755 100644 --- a/util/fptr_wlist.h +++ b/util/fptr_wlist.h @@ -211,9 +211,10 @@ int fptr_whitelist_hash_markdelfunc(lruhash_markdelfunc_type fptr); */ int fptr_whitelist_modenv_send_query(struct outbound_entry* (*fptr)( struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, - int nocaps, struct sockaddr_storage* addr, socklen_t addrlen, - uint8_t* zone, size_t zonelen, int tcp_upstream, int ssl_upstream, char* tls_auth_name, - struct module_qstate* q)); + int nocaps, int check_ratelimit, struct sockaddr_storage* addr, + socklen_t addrlen, uint8_t* zone, size_t zonelen, int tcp_upstream, + int ssl_upstream, char* tls_auth_name, struct module_qstate* q, + int* was_ratelimited)); /** * Check function pointer whitelist for module_env detach_subs callback values. diff --git a/util/module.h b/util/module.h index c6b7e573c..7a5480033 100644 --- a/util/module.h +++ b/util/module.h @@ -350,6 +350,7 @@ struct module_env { * EDNS, the answer is likely to be useless for this domain. * @param nocaps: do not use caps_for_id, use the qname as given. * (ignored if caps_for_id is disabled). + * @param check_ratelimit: if set, will check ratelimit before sending out. * @param addr: where to. * @param addrlen: length of addr. * @param zone: delegation point name. @@ -359,6 +360,8 @@ struct module_env { * @param tls_auth_name: if ssl_upstream, use this name with TLS * authentication. * @param q: which query state to reactivate upon return. + * @param was_ratelimited: it will signal back if the query failed to pass the + * ratelimit check. * @return: false on failure (memory or socket related). no query was * sent. Or returns an outbound entry with qsent and qstate set. * This outbound_entry will be used on later module invocations @@ -366,9 +369,10 @@ struct module_env { */ struct outbound_entry* (*send_query)(struct query_info* qinfo, uint16_t flags, int dnssec, int want_dnssec, int nocaps, + int check_ratelimit, struct sockaddr_storage* addr, socklen_t addrlen, uint8_t* zone, size_t zonelen, int tcp_upstream, int ssl_upstream, - char* tls_auth_name, struct module_qstate* q); + char* tls_auth_name, struct module_qstate* q, int* was_ratelimited); /** * Detach-subqueries.