Commit f59982b8 authored by Karel Slaný's avatar Karel Slaný Committed by Ondřej Surý

Using LRU cache to store DNS cookies.

parent 92511615
......@@ -657,6 +657,10 @@ static int cache_clear(lua_State *L)
lru_deinit(engine->resolver.cache_rep);
lru_init(engine->resolver.cache_rtt, LRU_RTT_SIZE);
lru_init(engine->resolver.cache_rep, LRU_REP_SIZE);
#if defined(ENABLE_COOKIES)
lru_deinit(engine->resolver.cache_cookie);
lru_init(engine->resolver.cache_cookie, LRU_COOKIES_SIZE);
#endif /* defined(ENABLE_COOKIES) */
lua_pushboolean(L, true);
return 1;
}
......
......@@ -469,6 +469,12 @@ static int init_resolver(struct engine *engine)
if (engine->resolver.cache_rep) {
lru_init(engine->resolver.cache_rep, LRU_REP_SIZE);
}
#if defined(ENABLE_COOKIES)
engine->resolver.cache_cookie = mm_alloc(engine->pool, lru_size(kr_cookie_lru_t, LRU_COOKIES_SIZE));
if (engine->resolver.cache_cookie) {
lru_init(engine->resolver.cache_cookie, LRU_COOKIES_SIZE);
}
#endif /* defined(ENABLE_COOKIES) */
/* Load basic modules */
#if defined(ENABLE_COOKIES)
......@@ -590,6 +596,9 @@ void engine_deinit(struct engine *engine)
kr_cache_close(&engine->resolver.cache);
lru_deinit(engine->resolver.cache_rtt);
lru_deinit(engine->resolver.cache_rep);
#if defined(ENABLE_COOKIES)
lru_deinit(engine->resolver.cache_cookie);
#endif /* defined(ENABLE_COOKIES) */
/* Clear IPC pipes */
for (size_t i = 0; i < engine->ipc_set.len; ++i) {
......
......@@ -23,6 +23,11 @@
#ifndef LRU_REP_SIZE
#define LRU_REP_SIZE (LRU_RTT_SIZE / 4) /**< NS reputation cache size */
#endif
#if defined(ENABLE_COOKIES)
#ifndef LRU_COOKIES_SIZE
#define LRU_COOKIES_SIZE 65536 /**< DNS cookies cache size. */
#endif
#endif /* defined(ENABLE_COOKIES) */
#ifndef MP_FREELIST_SIZE
#define MP_FREELIST_SIZE 64 /**< Maximum length of the worker mempool freelist */
#endif
......
......@@ -487,7 +487,7 @@ static bool subreq_update_cookies(struct qr_task *task, uv_udp_t *handle,
#endif /* 0 */
kr_request_put_cookie(&clnt_sett->current,
&task->worker->engine->resolver.cache,
task->worker->engine->resolver.cache_cookie,
(struct sockaddr*) sockaddr_ptr, srvr_addr, pkt);
return true;
......
......@@ -37,6 +37,3 @@ extern const struct knot_sc_alg *const kr_sc_algs[];
/** Binds server algorithm identifiers onto names. */
KR_EXPORT
extern const knot_lookup_t kr_sc_alg_names[];
/** Maximal size of a cookie option. */
#define KR_COOKIE_OPT_MAX_LEN (KNOT_EDNS_OPTION_HDRLEN + KNOT_OPT_COOKIE_CLNT + KNOT_OPT_COOKIE_SRVR_MAX)
/* Copyright (C) 2014 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
......@@ -14,222 +14,52 @@
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <libknot/rrtype/opt.h>
#include <string.h>
#include <libknot/db/db_lmdb.h>
#include <libknot/cookies/client.h>
#include "contrib/cleanup.h"
#include "lib/cdb_lmdb.h"
#include "lib/cookies/cache.h"
#include "lib/cookies/control.h"
#include "lib/utils.h"
/* Key size */
#define KEY_HSIZE (sizeof(uint8_t))
#define KEY_SIZE (KEY_HSIZE + 16)
/* Shorthand for operations on cache backend */
#define cache_isvalid(cache) ((cache) && (cache)->api && (cache)->db)
#define cache_op(cache, op, ...) (cache)->api->op((cache)->db, ## __VA_ARGS__)
/**
* @internal Composed key as { u8 tag, u8[4,16] IP address }
*/
static size_t cache_key(uint8_t *buf, uint8_t tag, const struct sockaddr *sa)
{
assert(buf && sa);
const char *addr = kr_inaddr(sa);
int addr_len = kr_inaddr_len(sa);
if (!addr || (addr_len <= 0)) {
return 0;
}
buf[0] = tag;
memcpy(buf + sizeof(uint8_t), addr, addr_len);
return addr_len + KEY_HSIZE;
}
static struct kr_cache_entry *lookup(struct kr_cache *cache, uint8_t tag,
const struct sockaddr *sa)
const uint8_t *kr_cookie_lru_get(kr_cookie_lru_t *cache,
const struct sockaddr *sa)
{
if (!cache || !sa) {
return NULL;
}
uint8_t keybuf[KEY_SIZE];
size_t key_len = cache_key(keybuf, tag, sa);
/* Look up and return value */
knot_db_val_t key = { keybuf, key_len };
knot_db_val_t val = { NULL, 0 };
int ret = cache_op(cache, read, &key, &val, 1);
if (ret != KNOT_EOK) {
int addr_len = kr_inaddr_len(sa);
const char *addr = kr_inaddr(sa);
if (!addr || addr_len <= 0) {
return NULL;
}
return (struct kr_cache_entry *)val.data;
}
static int check_lifetime(struct kr_cache_entry *found, uint32_t *timestamp)
{
/* No time constraint */
if (!timestamp) {
return kr_ok();
} else if (*timestamp <= found->timestamp) {
/* John Connor record cached in the future. */
*timestamp = 0;
return kr_ok();
} else {
/* Check if the record is still valid. */
uint32_t drift = *timestamp - found->timestamp;
if (drift <= found->ttl) {
*timestamp = drift;
return kr_ok();
}
}
return kr_error(ESTALE);
}
int kr_cookie_cache_peek(struct kr_cache *cache, uint8_t tag,
const struct sockaddr *sa, struct kr_cache_entry **entry,
uint32_t *timestamp)
{
if (!cache_isvalid(cache) || !sa || !entry) {
return kr_error(EINVAL);
}
struct kr_cache_entry *found = lookup(cache, tag, sa);
if (!found) {
cache->stats.miss += 1;
return kr_error(ENOENT);
}
/* Check entry lifetime */
*entry = found;
int ret = check_lifetime(found, timestamp);
if (ret == 0) {
cache->stats.hit += 1;
} else {
cache->stats.miss += 1;
}
return ret;
}
static void entry_write(struct kr_cache_entry *dst, struct kr_cache_entry *header, knot_db_val_t data)
{
assert(dst && header);
memcpy(dst, header, sizeof(*header));
if (data.data) {
memcpy(dst->data, data.data, data.len);
}
struct cookie_opt_data *cached = lru_get(cache, addr, addr_len);
return cached ? cached->opt_data : NULL;
}
int kr_cookie_cache_insert(struct kr_cache *cache,
uint8_t tag, const struct sockaddr *sa,
struct kr_cache_entry *header, knot_db_val_t data)
int kr_cookie_lru_set(kr_cookie_lru_t *cache, const struct sockaddr *sa,
uint8_t *opt)
{
if (!cache_isvalid(cache) || !sa || !header) {
if (!cache || !sa) {
return kr_error(EINVAL);
}
/* Insert key */
uint8_t keybuf[KEY_SIZE];
size_t key_len = cache_key(keybuf, tag, sa);
if (key_len == 0) {
return kr_error(EILSEQ);
}
assert(data.len != 0);
knot_db_val_t key = { keybuf, key_len };
knot_db_val_t entry = { NULL, sizeof(*header) + data.len };
/* LMDB can do late write and avoid copy */
int ret = 0;
cache->stats.insert += 1;
if (cache->api == kr_cdb_lmdb()) {
ret = cache_op(cache, write, &key, &entry, 1);
if (ret != 0) {
return ret;
}
entry_write(entry.data, header, data);
ret = cache_op(cache, sync); /* Make sure the entry is comitted. */
} else {
/* Other backends must prepare contiguous data first */
auto_free char *buffer = malloc(entry.len);
entry.data = buffer;
entry_write(entry.data, header, data);
ret = cache_op(cache, write, &key, &entry, 1);
if (!opt) {
return kr_ok();
}
return ret;
}
int kr_cookie_cache_remove(struct kr_cache *cache,
uint8_t tag, const struct sockaddr *sa)
{
if (!cache_isvalid(cache) || !sa) {
int addr_len = kr_inaddr_len(sa);
const char *addr = kr_inaddr(sa);
if (!addr || addr_len <= 0) {
return kr_error(EINVAL);
}
uint8_t keybuf[KEY_SIZE];
size_t key_len = cache_key(keybuf, tag, sa);
if (key_len == 0) {
return kr_error(EILSEQ);
struct cookie_opt_data *cached = lru_set(cache, addr, addr_len);
if (!cached) {
return kr_error(ENOMEM);
}
knot_db_val_t key = { keybuf, key_len };
cache->stats.delete += 1;
return cache_op(cache, remove, &key, 1);
}
int kr_cookie_cache_peek_cookie(struct kr_cache *cache, const struct sockaddr *sa,
struct timed_cookie *cookie, uint32_t *timestamp)
{
if (!cache_isvalid(cache) || !sa || !cookie || !timestamp) {
return kr_error(EINVAL);
}
memcpy(cached->opt_data, opt, knot_edns_opt_get_length(opt));
/* Check if the RRSet is in the cache. */
struct kr_cache_entry *entry = NULL;
int ret = kr_cookie_cache_peek(cache, KR_CACHE_COOKIE, sa,
&entry, timestamp);
if (ret != 0) {
return ret;
}
cookie->ttl = entry->ttl;
cookie->cookie_opt = entry->data;
return kr_ok();
}
int kr_cookie_cache_insert_cookie(struct kr_cache *cache, const struct sockaddr *sa,
const struct timed_cookie *cookie,
uint32_t timestamp)
{
if (!cache_isvalid(cache) || !sa) {
return kr_error(EINVAL);
}
/* Ignore empty cookie data. */
if (!cookie || !cookie->cookie_opt) {
return kr_ok();
}
/* Prepare header to write. */
struct kr_cache_entry header = {
.timestamp = timestamp,
.ttl = cookie->ttl,
.rank = KR_RANK_BAD,
.flags = KR_CACHE_FLAG_NONE,
.count = 1 /* Only one entry. */
};
size_t cookie_opt_size = KNOT_EDNS_OPTION_HDRLEN +
knot_edns_opt_get_length(cookie->cookie_opt);
knot_db_val_t data = { (uint8_t *) cookie->cookie_opt, cookie_opt_size };
return kr_cookie_cache_insert(cache, KR_CACHE_COOKIE, sa,
&header, data);
}
......@@ -16,93 +16,47 @@
#pragma once
#include <libknot/rrtype/opt-cookie.h>
#include <netinet/in.h>
#include <stdint.h>
#include "lib/cache.h"
#include "lib/defines.h"
#include "lib/generic/lru.h"
/** DNS cookie cache entry tag. */
#define KR_CACHE_COOKIE (KR_CACHE_USER + 'C')
/** Maximal size of a cookie option. */
#define KR_COOKIE_OPT_MAX_LEN (KNOT_EDNS_OPTION_HDRLEN + KNOT_OPT_COOKIE_CLNT + KNOT_OPT_COOKIE_SRVR_MAX)
/**
* Peek the cache for asset (tag, socket address).
* @note The 'drift' is the time passed between the inception time and now (in seconds).
* @param cache cache structure
* @param tag asset tag
* @param sa asset socket address
* @param entry cache entry, will be set to valid pointer or NULL
* @param timestamp current time (will be replaced with drift if successful)
* @return 0 or an error code
* Cookie option entry.
*/
KR_EXPORT
int kr_cookie_cache_peek(struct kr_cache *cache,
uint8_t tag, const struct sockaddr *sa,
struct kr_cache_entry **entry, uint32_t *timestamp);
/**
* Insert asset into cache, replacing any existing data.
* @param cache cache structure
* @param tag asset tag
* @param sa asset socket address
* @param header filled entry header (ttl and time stamp)
* @param data inserted data
* @return 0 or an error code
*/
KR_EXPORT
int kr_cookie_cache_insert(struct kr_cache *cache,
uint8_t tag, const struct sockaddr *sa,
struct kr_cache_entry *header, knot_db_val_t data);
/**
* Remove asset from cache.
* @param cache cache structure
* @param tag asset tag
* @param sa asset socket address
* @return 0 or an error code
*/
KR_EXPORT
int kr_cookie_cache_remove(struct kr_cache *cache,
uint8_t tag, const struct sockaddr *sa);
/**
* Structure used for cookie cache interface.
* @note There is no other way how to pass a ttl into a cookie.
*/
struct timed_cookie {
uint32_t ttl;
const uint8_t *cookie_opt;
struct cookie_opt_data {
uint8_t opt_data[KR_COOKIE_OPT_MAX_LEN];
};
/**
* Peek the cache for given cookie (socket address)
* @note The 'drift' is the time passed between the cache time of the cookie and now (in seconds).
* @param cache cache structure
* @param sa socket address
* @param cookie asset
* @param timestamp current time (will be replaced with drift if successful)
* @return 0 or an error code
* DNS cookies tracking.
*/
KR_EXPORT
int kr_cookie_cache_peek_cookie(struct kr_cache *cache, const struct sockaddr *sa,
struct timed_cookie *cookie, uint32_t *timestamp);
typedef lru_hash(struct cookie_opt_data) kr_cookie_lru_t;
/**
* Insert a DNS cookie (client and server) entry for the given server signature (IP address).
* @param cache cache structure
* @param sa server IP address
* @param cookie ttl and whole EDNS cookie option (header, client and server cookies)
* @param timestamp current time
* @return 0 or an error code
* @brief Obtain LRU cache entry.
*
* @param cache cookie LRU cache
* @param sa socket address serving as key
* @return pointer to cached option or NULL if not found or error occurred
*/
KR_EXPORT
int kr_cookie_cache_insert_cookie(struct kr_cache *cache, const struct sockaddr *sa,
const struct timed_cookie *cookie,
uint32_t timestamp);
const uint8_t *kr_cookie_lru_get(kr_cookie_lru_t *cache,
const struct sockaddr *sa);
/**
* Remove asset from cache.
* @param cache cache structure
* @param sa socket address
* @return 0 or an error code
* @brief Stores cookie option into LRU cache.
*
* @param cache cookie LRU cache
* @param sa socket address serving as key
* @param opt cookie option to be stored
* @return kr_ok() or error code
*/
#define kr_cookie_cache_remove_cookie(cache, sa) \
kr_cookie_cache_remove((cache), KR_CACHE_COOKIE, (sa))
KR_EXPORT
int kr_cookie_lru_set(kr_cookie_lru_t *cache, const struct sockaddr *sa,
uint8_t *opt);
......@@ -15,6 +15,7 @@
*/
#include <assert.h>
#include <libknot/rrtype/opt.h>
#include <libknot/rrtype/opt-cookie.h>
#include "lib/cookies/cache.h"
......@@ -25,31 +26,21 @@
* @brief Check whether there is a cached cookie that matches the current
* client cookie.
*/
static const uint8_t *peek_and_check_cc(struct kr_cache *cache,
const void *sockaddr,
static const uint8_t *peek_and_check_cc(kr_cookie_lru_t *cache, const void *sa,
const uint8_t *cc, uint16_t cc_len)
{
assert(cache && sockaddr && cc && cc_len);
assert(cache && sa && cc && cc_len);
uint32_t timestamp = 0;
struct timed_cookie timed_cookie = { 0, };
int ret = kr_cookie_cache_peek_cookie(cache, sockaddr, &timed_cookie,
&timestamp);
if (ret != kr_ok()) {
const uint8_t *cached_opt = kr_cookie_lru_get(cache, sa);
if (!cached_opt) {
return NULL;
}
assert(timed_cookie.cookie_opt);
/* Ignore the time stamp and time to leave. If the cookie is in cache
* then just use it. The cookie control should be performed in the
* cookie module/layer. */
const uint8_t *cached_cc = knot_edns_opt_get_data((uint8_t *) timed_cookie.cookie_opt);
const uint8_t *cached_cc = knot_edns_opt_get_data((uint8_t *) cached_opt);
if (cc_len == KNOT_OPT_COOKIE_CLNT &&
0 == memcmp(cc, cached_cc, cc_len)) {
return timed_cookie.cookie_opt;
return cached_opt;
}
return NULL;
......@@ -112,7 +103,7 @@ static int opt_rr_add_cc(knot_rrset_t *opt_rr, uint8_t *cc, uint16_t cc_len,
}
int kr_request_put_cookie(const struct kr_cookie_comp *clnt_comp,
struct kr_cache *cookie_cache,
kr_cookie_lru_t *cookie_cache,
const struct sockaddr *clnt_sa,
const struct sockaddr *srvr_sa,
knot_pkt_t *pkt)
......
......@@ -16,13 +16,12 @@
#pragma once
#include <libknot/rrtype/opt-cookie.h>
#include <libknot/packet/pkt.h>
#include "lib/cookies/alg_containers.h"
#include "lib/cookies/cache.h"
#include "lib/cookies/control.h"
#include "lib/cookies/nonce.h"
#include "lib/cache.h"
#include "lib/defines.h"
/**
......@@ -37,7 +36,7 @@
*/
KR_EXPORT
int kr_request_put_cookie(const struct kr_cookie_comp *clnt_comp,
struct kr_cache *cookie_cache,
kr_cookie_lru_t *cookie_cache,
const struct sockaddr *clnt_sa,
const struct sockaddr *srvr_sa,
knot_pkt_t *pkt);
......
......@@ -171,64 +171,48 @@ static int srvr_sockaddr_cc_check(const struct sockaddr **sockaddr,
* Obtain cookie from cache.
* @note The ttl and current time are respected. Outdated entries are ignored.
* @param cache cache context
* @param sockaddr key value
* @param timestamp current time
* @param remove_outdated true if outdated entries should be removed
* @param sa key value
* @param cookie_opt entire EDNS cookie option (including header)
* @return true if a cookie exists in cache
*/
static bool materialise_cookie_opt(struct kr_cache *cache,
const struct sockaddr *sockaddr,
uint32_t timestamp, bool remove_outdated,
static bool materialise_cookie_opt(kr_cookie_lru_t *cache,
const struct sockaddr *sa,
uint8_t cookie_opt[KR_COOKIE_OPT_MAX_LEN])
{
assert(cache && sockaddr);
assert(cache && sa);
bool found = false;
struct timed_cookie timed_cookie = { 0, };
int ret = kr_cookie_cache_peek_cookie(cache, sockaddr, &timed_cookie,
&timestamp);
if (ret != kr_ok()) {
const uint8_t *cached_cookie_opt = kr_cookie_lru_get(cache, sa);
if (!cached_cookie_opt) {
return false;
}
assert(timed_cookie.cookie_opt);
if (remove_outdated && (timed_cookie.ttl < timestamp)) {
/* Outdated entries must be removed. */
DEBUG_MSG(NULL, "%s\n", "removing outdated entry from cache");
kr_cookie_cache_remove_cookie(cache, sockaddr);
size_t cookie_opt_size = KNOT_EDNS_OPTION_HDRLEN +
knot_edns_opt_get_length(cached_cookie_opt);
if (cookie_opt_size > KR_COOKIE_OPT_MAX_LEN) {
return false;
}
size_t cookie_opt_size = KNOT_EDNS_OPTION_HDRLEN +
knot_edns_opt_get_length(timed_cookie.cookie_opt);
assert(cookie_opt_size <= KR_COOKIE_OPT_MAX_LEN);
if (cookie_opt) {
memcpy(cookie_opt, timed_cookie.cookie_opt, cookie_opt_size);
memcpy(cookie_opt, cached_cookie_opt, cookie_opt_size);
}
return true;
}
/**
* Check whether the supplied cookie is cached under the given key.
* @param cache cache context
* @param sockaddr key value
* @param timestamp current time
* @param sa key value
* @param cookie_opt cookie option to search for
*/
static bool is_cookie_cached(struct kr_cache *cache,
const struct sockaddr *sockaddr,
uint32_t timestamp,
static bool is_cookie_cached(kr_cookie_lru_t *cache, const struct sockaddr *sa,
const uint8_t *cookie_opt)
{
assert(cache && sockaddr && cookie_opt);
assert(cache && sa && cookie_opt);
uint8_t cached_opt[KR_COOKIE_OPT_MAX_LEN];
bool have_cached = materialise_cookie_opt(cache, sockaddr, timestamp,
false, cached_opt);
bool have_cached = materialise_cookie_opt(cache, sa, cached_opt);
if (!have_cached) {
return false;
}
......@@ -249,10 +233,9 @@ static bool is_cookie_cached(struct kr_cache *cache,
* Check cookie content and store it to cache.
*/
static bool check_cookie_content_and_cache(const struct kr_cookie_settings *clnt_sett,
uint32_t cookie_cache_ttl,
struct kr_query *qry,
uint8_t *pkt_cookie_opt,
struct kr_cache *cache)
kr_cookie_lru_t *cache)
{
assert(clnt_sett && qry && pkt_cookie_opt && cache);
......@@ -286,13 +269,8 @@ static bool check_cookie_content_and_cache(const struct kr_cookie_settings *clnt
/* Don't cache received cookies that don't match the current secret. */
if (returned_current &&
!is_cookie_cached(cache, srvr_sockaddr, qry->timestamp.tv_sec,
pkt_cookie_opt)) {
struct timed_cookie timed_cookie = { cookie_cache_ttl, pkt_cookie_opt };
ret = kr_cookie_cache_insert_cookie(cache, srvr_sockaddr,
&timed_cookie,
qry->timestamp.tv_sec);
!is_cookie_cached(cache, srvr_sockaddr, pkt_cookie_opt)) {
ret = kr_cookie_lru_set(cache, srvr_sockaddr, pkt_cookie_opt);
if (ret != kr_ok()) {
DEBUG_MSG(NULL, "%s\n", "failed caching cookie");
} else {
......@@ -321,13 +299,12 @@ static int check_response(knot_layer_t *ctx, knot_pkt_t *pkt)
KNOT_EDNS_OPTION_COOKIE);
}
struct kr_cache *cookie_cache = &req->ctx->cache;
kr_cookie_lru_t *cookie_cache = req->ctx->cache_cookie;
const struct sockaddr *srvr_sockaddr = passed_server_sockaddr(qry);
if (!pkt_cookie_opt && srvr_sockaddr &&
materialise_cookie_opt(cookie_cache, srvr_sockaddr,
qry->timestamp.tv_sec, true, NULL)) {
materialise_cookie_opt(cookie_cache, srvr_sockaddr, NULL)) {
/* We haven't received any cookies although we should. */
DEBUG_MSG(NULL, "%s\n",
"expected to receive a cookie but none received");
......@@ -339,8 +316,7 @@ static int check_response(knot_layer_t *ctx, knot_pkt_t *pkt)
return ctx->state;
}
if (!check_cookie_content_and_cache(&cookie_ctx->clnt,
cookie_ctx->cache_ttl, qry,
if (!check_cookie_content_and_cache(&cookie_ctx->clnt, qry,
pkt_cookie_opt, cookie_cache)) {
return KNOT_STATE_FAIL;
}
......
......@@ -19,6 +19,7 @@
#include <netinet/in.h>
#include <libknot/packet/pkt.h>
#include "lib/cookies/cache.h"
#include "lib/cookies/control.h"
#include "lib/layer.h"
#include "lib/generic/map.h"
......@@ -97,6 +98,7 @@ struct kr_context
/* The structure should not be held within the cookies module because
* of better access. */
struct kr_cookie_ctx cookie_ctx;
kr_cookie_lru_t *cache_cookie;
#endif /* defined(ENABLE_COOKIES) */
knot_mm_t *pool;
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment