diff options
author | Vsevolod Stakhov <vsevolod@highsecure.ru> | 2019-10-31 17:12:16 +0000 |
---|---|---|
committer | Vsevolod Stakhov <vsevolod@highsecure.ru> | 2019-10-31 17:12:16 +0000 |
commit | e3057e5e4623e9075602b2c4c20346c8479510fe (patch) | |
tree | 6e3566630b5d27d6e48d4120845b0c3c619f3ef6 | |
parent | 47f0c0636b7d5c6a22b5ae7d733b54b873e3e227 (diff) | |
download | rspamd-e3057e5e4623e9075602b2c4c20346c8479510fe.tar.gz rspamd-e3057e5e4623e9075602b2c4c20346c8479510fe.zip |
[Minor] DNS: Add servfail cache
-rw-r--r-- | src/libserver/dns.c | 150 | ||||
-rw-r--r-- | src/libserver/dns.h | 3 |
2 files changed, 145 insertions, 8 deletions
diff --git a/src/libserver/dns.c b/src/libserver/dns.c index 08454c656..710f96b3a 100644 --- a/src/libserver/dns.c +++ b/src/libserver/dns.c @@ -57,6 +57,36 @@ struct rspamd_dns_request_ud { struct rdns_reply *reply; }; +struct rspamd_dns_fail_cache_entry { + const char *name; + gint32 namelen; + enum rdns_request_type type; +}; + +static guint +rspamd_dns_fail_hash (gconstpointer ptr) +{ + struct rspamd_dns_fail_cache_entry *elt = + (struct rspamd_dns_fail_cache_entry *)ptr; + + /* We don't care about type when doing hashing */ + return rspamd_cryptobox_fast_hash (elt->name, elt->namelen, + rspamd_hash_seed ()); +} + +static gboolean +rspamd_dns_fail_equal (gconstpointer p1, gconstpointer p2) +{ + struct rspamd_dns_fail_cache_entry *e1 = (struct rspamd_dns_fail_cache_entry *)p1, + *e2 = (struct rspamd_dns_fail_cache_entry *)p2; + + if (e1->type == e2->type && e1->namelen == e2->namelen) { + return memcmp (e1->name, e2->name, e1->namelen) == 0; + } + + return FALSE; +} + static void rspamd_dns_fin_cb (gpointer arg) { @@ -100,13 +130,41 @@ rspamd_dns_callback (struct rdns_reply *reply, gpointer ud) reqdata->reply = reply; + if (reqdata->session) { + if (reply->code == RDNS_RC_SERVFAIL && + reqdata->task && + reqdata->task->resolver->fails_cache) { + + /* Add to cache... */ + const gchar *name = reqdata->req->requested_names[0].name; + gchar *target; + gsize namelen; + struct rspamd_dns_fail_cache_entry *nentry; + + /* Allocate in a single entry to allow further free in a single call */ + namelen = strlen (name); + nentry = g_malloc (sizeof (nentry) + namelen + 1); + target = ((gchar *)nentry) + sizeof (nentry); + rspamd_strlcpy (target, name, namelen + 1); + nentry->type = reqdata->req->requested_names[0].type; + nentry->name = target; + nentry->namelen = namelen; + + /* Rdns request is retained there */ + rspamd_lru_hash_insert (reqdata->task->resolver->fails_cache, + nentry, rdns_request_retain (reply->request), + reqdata->task->task_timestamp, + reqdata->task->resolver->fails_cache_time); + } + /* * Ref event to avoid double unref by * event removing */ rdns_request_retain (reply->request); - rspamd_session_remove_event (reqdata->session, rspamd_dns_fin_cb, reqdata); + rspamd_session_remove_event (reqdata->session, + rspamd_dns_fin_cb, reqdata); } else { reqdata->cb (reply, reqdata->ud); @@ -177,13 +235,38 @@ rspamd_dns_resolver_request (struct rspamd_dns_resolver *resolver, return reqdata; } +struct rspamd_dns_cached_delayed_cbdata { + struct rspamd_task *task; + dns_callback_type cb; + gpointer ud; + ev_timer tm; + struct rdns_request *req; +}; + +static void +rspamd_fail_cache_cb (EV_P_ ev_timer *w, int revents) +{ + struct rspamd_dns_cached_delayed_cbdata *cbd = + (struct rspamd_dns_cached_delayed_cbdata *)w->data; + struct rdns_reply fake_reply; + + ev_timer_stop (EV_A_ w); + memset (&fake_reply, 0, sizeof (fake_reply)); + fake_reply.code = RDNS_RC_SERVFAIL; + fake_reply.request = cbd->req; + fake_reply.resolver = cbd->req->resolver; + fake_reply.requested_name = cbd->req->requested_names[0].name; + cbd->cb (&fake_reply, cbd->ud); + rdns_request_release (cbd->req); +} + static gboolean make_dns_request_task_common (struct rspamd_task *task, - dns_callback_type cb, - gpointer ud, - enum rdns_request_type type, - const char *name, - gboolean forced) + dns_callback_type cb, + gpointer ud, + enum rdns_request_type type, + const char *name, + gboolean forced) { struct rspamd_dns_request_ud *reqdata; @@ -191,7 +274,37 @@ make_dns_request_task_common (struct rspamd_task *task, return FALSE; } - reqdata = rspamd_dns_resolver_request (task->resolver, task->s, task->task_pool, cb, ud, + if (task->resolver->fails_cache) { + /* Search in failures cache */ + struct rspamd_dns_fail_cache_entry search; + struct rdns_request *req; + + search.name = name; + search.namelen = strlen (name); + search.type = type; + + if ((req = rspamd_lru_hash_lookup (task->resolver->fails_cache, + &search, task->task_timestamp)) != NULL) { + /* + * We need to reply with SERVFAIL again to the API, so add a special + * timer, uh-oh, and fire it + */ + struct rspamd_dns_cached_delayed_cbdata *cbd = + rspamd_mempool_alloc0 (task->task_pool, sizeof (*cbd)); + + ev_timer_init (&cbd->tm, rspamd_fail_cache_cb, 0.0, 0.0); + cbd->task = task; + cbd->cb = cb; + cbd->ud = ud; + cbd->req = rdns_request_retain (req); + cbd->tm.data = cbd; + + return TRUE; + } + } + + reqdata = rspamd_dns_resolver_request ( + task->resolver, task->s, task->task_pool, cb, ud, type, name); if (reqdata) { @@ -531,7 +644,8 @@ rspamd_dns_resolver_config_ucl (struct rspamd_config *cfg, struct rspamd_dns_resolver *dns_resolver, const ucl_object_t *dns_section) { - const ucl_object_t *fake_replies; + const ucl_object_t *fake_replies, *fails_cache_size, *fails_cache_time; + static const ev_tstamp default_fails_cache_time = 10.0; /* Process fake replies */ fake_replies = ucl_object_lookup_any (dns_section, "fake_records", @@ -544,6 +658,22 @@ rspamd_dns_resolver_config_ucl (struct rspamd_config *cfg, rspamd_process_fake_reply (cfg, dns_resolver, cur_arr); } } + + fails_cache_size = ucl_object_lookup (dns_section, "fails_cache_size"); + if (fails_cache_size && ucl_object_type (fails_cache_size) == UCL_INT) { + + dns_resolver->fails_cache_time = default_fails_cache_time; + fails_cache_time = ucl_object_lookup (dns_section, "fails_cache_time"); + + if (fails_cache_time) { + dns_resolver->fails_cache_time = ucl_object_todouble (fails_cache_time); + } + + dns_resolver->fails_cache = rspamd_lru_hash_new_full ( + ucl_object_toint (fails_cache_size), + g_free, (GDestroyNotify)rdns_request_release, + rspamd_dns_fail_hash, rspamd_dns_fail_equal); + } } struct rspamd_dns_resolver * @@ -655,6 +785,10 @@ rspamd_dns_resolver_deinit (struct rspamd_dns_resolver *resolver) rspamd_upstreams_destroy (resolver->ups); } + if (resolver->fails_cache) { + rspamd_lru_hash_destroy (resolver->fails_cache); + } + g_free (resolver); } } diff --git a/src/libserver/dns.h b/src/libserver/dns.h index 87cc0954b..367053ef4 100644 --- a/src/libserver/dns.h +++ b/src/libserver/dns.h @@ -23,6 +23,7 @@ #include "logger.h" #include "rdns.h" #include "upstream.h" +#include "libutil/hash.h" #ifdef __cplusplus extern "C" { @@ -34,6 +35,8 @@ struct rspamd_task; struct rspamd_dns_resolver { struct rdns_resolver *r; struct ev_loop *event_loop; + rspamd_lru_hash_t *fails_cache; + ev_tstamp fails_cache_time; struct upstream_list *ups; struct rspamd_config *cfg; gdouble request_timeout; |