Skip to content

Commit

Permalink
Merge pull request #616 from NLnetLabs/bugfix/ratelimit
Browse files Browse the repository at this point in the history
Update ratelimit logic
  • Loading branch information
gthess committed Feb 2, 2022
2 parents 25eae98 + 7ddd456 commit 358e3a5
Show file tree
Hide file tree
Showing 40 changed files with 4,908 additions and 5,045 deletions.
8 changes: 6 additions & 2 deletions daemon/remote.c
Original file line number Diff line number Diff line change
Expand Up @@ -2865,6 +2865,8 @@ struct ratelimit_list_arg {
int all;
/** current time */
time_t now;
/** if backoff is enabled */
int backoff;
};

#define ip_ratelimit_list_arg ratelimit_list_arg
Expand All @@ -2878,7 +2880,7 @@ rate_list(struct lruhash_entry* e, void* arg)
struct rate_data* d = (struct rate_data*)e->data;
char buf[257];
int lim = infra_find_ratelimit(a->infra, k->name, k->namelen);
int max = infra_rate_max(d, a->now);
int max = infra_rate_max(d, a->now, a->backoff);
if(a->all == 0) {
if(max < lim)
return;
Expand All @@ -2896,7 +2898,7 @@ ip_rate_list(struct lruhash_entry* e, void* arg)
struct ip_rate_key* k = (struct ip_rate_key*)e->key;
struct ip_rate_data* d = (struct ip_rate_data*)e->data;
int lim = infra_ip_ratelimit;
int max = infra_rate_max(d, a->now);
int max = infra_rate_max(d, a->now, a->backoff);
if(a->all == 0) {
if(max < lim)
return;
Expand All @@ -2914,6 +2916,7 @@ do_ratelimit_list(RES* ssl, struct worker* worker, char* arg)
a.infra = worker->env.infra_cache;
a.now = *worker->env.now;
a.ssl = ssl;
a.backoff = worker->env.cfg->ratelimit_backoff;
arg = skipwhite(arg);
if(strcmp(arg, "+a") == 0)
a.all = 1;
Expand All @@ -2932,6 +2935,7 @@ do_ip_ratelimit_list(RES* ssl, struct worker* worker, char* arg)
a.infra = worker->env.infra_cache;
a.now = *worker->env.now;
a.ssl = ssl;
a.backoff = worker->env.cfg->ip_ratelimit_backoff;
arg = skipwhite(arg);
if(strcmp(arg, "+a") == 0)
a.all = 1;
Expand Down
18 changes: 11 additions & 7 deletions daemon/worker.c
Original file line number Diff line number Diff line change
Expand Up @@ -1167,7 +1167,8 @@ worker_handle_request(struct comm_point* c, void* arg, int error,

/* check if this query should be dropped based on source ip rate limiting */
if(!infra_ip_ratelimit_inc(worker->env.infra_cache, repinfo,
*worker->env.now, c->buffer)) {
*worker->env.now,
worker->env.cfg->ip_ratelimit_backoff, c->buffer)) {
/* See if we are passed through with slip factor */
if(worker->env.cfg->ip_ratelimit_factor != 0 &&
ub_random_max(worker->env.rnd,
Expand Down Expand Up @@ -1967,9 +1968,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(
Expand All @@ -1978,9 +1980,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;
}
Expand Down Expand Up @@ -2024,10 +2027,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;
Expand Down
10 changes: 6 additions & 4 deletions dnstap/unbound-dnstap-socket.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions doc/example.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -860,6 +860,10 @@ server:
# 0 blocks when ratelimited, otherwise let 1/xth traffic through
# ratelimit-factor: 10

# Aggressive rate limit when the limit is reached and until demand has
# decreased in a 2 second rate window.
# ratelimit-backoff: no

# override the ratelimit for a specific domain name.
# give this setting multiple times to have multiple overrides.
# ratelimit-for-domain: example.com 1000
Expand All @@ -880,6 +884,10 @@ server:
# 0 blocks when ip is ratelimited, otherwise let 1/xth traffic through
# ip-ratelimit-factor: 10

# Aggressive rate limit when the limit is reached and until demand has
# decreased in a 2 second rate window.
# ip-ratelimit-backoff: no

# Limit the number of connections simultaneous from a netblock
# tcp-connection-limit: 192.0.2.0/24 12

Expand Down
23 changes: 21 additions & 2 deletions doc/unbound.conf.5.in
Original file line number Diff line number Diff line change
Expand Up @@ -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<memory size>
Give the size of the data structure in which the current ongoing rates are
Expand All @@ -1670,6 +1671,15 @@ This can make ordinary queries complete (if repeatedly queried for),
and enter the cache, whilst also mitigating the traffic flow by the
factor given.
.TP 5
.B ratelimit\-backoff: \fI<yes or no>
If enabled, the ratelimit is treated as a hard failure instead of the default
maximum allowed constant rate. When the limit is reached, traffic is
ratelimited and demand continues to be kept track of for a 2 second rate
window. No traffic is allowed, except for ratelimit\-factor, until demand
decreases below the configured ratelimit for a 2 second rate window. Useful to
set ratelimit to a suspicious rate to aggressively limit unusually high
traffic. Default is off.
.TP 5
.B ratelimit\-for\-domain: \fI<domain> <number qps or 0>
Override the global ratelimit for an exact match domain name with the listed
number. You can give this for any number of names. For example, for
Expand All @@ -1686,7 +1696,7 @@ to use different settings for a top\-level\-domain and subdomains.
A value of 0 will disable ratelimiting for domain names that end in this name.
.TP 5
.B ip\-ratelimit: \fI<number or 0>
Enable global ratelimiting of queries accepted per ip address.
Enable global ratelimiting of queries accepted per IP address.
If 0, the default, it is disabled. This option is experimental at this time.
The ratelimit is in queries per second that are allowed. More queries are
completely dropped and will not receive a reply, SERVFAIL or otherwise.
Expand All @@ -1713,6 +1723,15 @@ This can make ordinary queries complete (if repeatedly queried for),
and enter the cache, whilst also mitigating the traffic flow by the
factor given.
.TP 5
.B ip\-ratelimit\-backoff: \fI<yes or no>
If enabled, the ratelimit is treated as a hard failure instead of the default
maximum allowed constant rate. When the limit is reached, traffic is
ratelimited and demand continues to be kept track of for a 2 second rate
window. No traffic is allowed, except for ip\-ratelimit\-factor, until demand
decreases below the configured ratelimit for a 2 second rate window. Useful to
set ip\-ratelimit to a suspicious rate to aggressively limit unusually high
traffic. Default is off.
.TP 5
.B outbound\-msg\-retry: \fI<number>
The number of retries Unbound will do in case of a non positive response is
received. If a forward nameserver is used, this is the number of retries per
Expand Down
84 changes: 21 additions & 63 deletions iterator/iterator.c
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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 ||
Expand Down
2 changes: 1 addition & 1 deletion iterator/iterator.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down
11 changes: 7 additions & 4 deletions libunbound/libworker.c
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down
Loading

0 comments on commit 358e3a5

Please sign in to comment.