Commit 7ffd3e4a authored by Marek Vavruša's avatar Marek Vavruša

Merge branch 'cache-improvements'

parents 01ec28f4 90d7ff42
......@@ -228,4 +228,16 @@
#define WARN_UNUSED_RESULT
#endif
#endif
#ifndef NON_NULL
#if HAVE_ATTRIBUTE_NONNULL
/**
* NON_NULL - nonnull attribute specifies that some function parameters should be non-null pointers.
*/
#define NON_NULL(...) __attribute__((nonnull(__VA_ARGS__)))
#else
#define NON_NULL(...)
#endif
#endif
#endif /* CCAN_COMPILER_H */
......@@ -2,4 +2,5 @@
#define HAVE_ATTRIBUTE_COLD 1
#define HAVE_ATTRIBUTE_NORETURN 1
#define HAVE_ATTRIBUTE_PURE 1
#define HAVE_ATTRIBUTE_UNUSED 1
\ No newline at end of file
#define HAVE_ATTRIBUTE_UNUSED 1
#define HAVE_ATTRIBUTE_NONNULL 1
\ No newline at end of file
......@@ -328,7 +328,8 @@ static int cache_count(lua_State *L)
lua_error(L);
}
lua_pushinteger(L, storage->count(&txn.t));
/* First key is a version counter, omit it. */
lua_pushinteger(L, storage->count(&txn.t) - 1);
kr_cache_txn_abort(&txn);
return 1;
}
......
......@@ -212,7 +212,7 @@ static int l_trampoline(lua_State *L)
return 0;
}
JsonNode *root_node = json_decode(ret);
if (root_node->tag == JSON_OBJECT || root_node->tag == JSON_ARRAY) {
if (root_node && (root_node->tag == JSON_OBJECT || root_node->tag == JSON_ARRAY)) {
l_unpack_json(L, root_node);
} else {
lua_pushstring(L, ret);
......
......@@ -46,9 +46,10 @@ setmetatable(modules, {
if not rawget(_G, k) then
modules.load(k)
local mod = _G[k]
if mod and mod['config'] then
if k ~= v then mod['config'](v)
else mod['config']()
local config = rawget(mod, 'config')
if mod and config then
if k ~= v then config(v)
else config()
end
end
end
......
......@@ -29,22 +29,58 @@
#include "lib/defines.h"
#include "lib/utils.h"
/* Cache version */
#define KEY_VERSION "V\x01"
/* Key size */
#define KEY_HSIZE (1 + sizeof(uint16_t))
#define KEY_HSIZE (sizeof(uint8_t) + sizeof(uint16_t))
#define KEY_SIZE (KEY_HSIZE + KNOT_DNAME_MAXLEN)
#define txn_api(txn) (txn->owner->api)
#define txn_api(txn) ((txn)->owner->api)
/** @internal Check cache internal data version. Clear if it doesn't match. */
static void assert_right_version(struct kr_cache *cache)
{
/* Check cache ABI version */
struct kr_cache_txn txn;
int ret = kr_cache_txn_begin(cache, &txn, 0);
if (ret != 0) {
return; /* N/A, doesn't work. */
}
namedb_val_t key = { KEY_VERSION, 2 };
namedb_val_t val = { NULL, 0 };
ret = txn_api(&txn)->find(&txn.t, &key, &val, 0);
if (ret == 0) { /* Version is OK */
kr_cache_txn_abort(&txn);
return;
}
/* Recreate cache and write version key */
ret = txn_api(&txn)->count(&txn.t);
if (ret > 0) { /* Non-empty cache, purge it. */
log_info("[cache] version mismatch, clearing\n");
kr_cache_clear(&txn);
kr_cache_txn_commit(&txn);
ret = kr_cache_txn_begin(cache, &txn, 0);
}
/* Either purged or empty. */
if (ret == 0) {
txn_api(&txn)->insert(&txn.t, &key, &val, 0);
kr_cache_txn_commit(&txn);
}
}
int kr_cache_open(struct kr_cache *cache, const namedb_api_t *api, void *opts, mm_ctx_t *mm)
{
if (!cache) {
return kr_error(EINVAL);
}
/* Open cache */
cache->api = (api == NULL) ? namedb_lmdb_api() : api;
int ret = cache->api->init(&cache->db, mm, opts);
if (ret != 0) {
return ret;
}
memset(&cache->stats, 0, sizeof(cache->stats));
/* Check cache ABI version */
assert_right_version(cache);
return kr_ok();
}
......@@ -99,20 +135,23 @@ void kr_cache_txn_abort(struct kr_cache_txn *txn)
}
}
/** @internal Composed key as { u8 tag, u8[1-255] name, u16 type } */
/**
* @internal Composed key as { u8 tag, u8[1-255] name, u16 type }
* The name is lowercased and label order is reverted for easy prefix search.
* e.g. '\x03nic\x02cz\x00' is saved as '\0x00cz\x00nic\x00'
*/
static size_t cache_key(uint8_t *buf, uint8_t tag, const knot_dname_t *name, uint16_t rrtype)
{
/* Write tag + type */
buf[0] = tag;
memcpy(buf + 1, &rrtype, sizeof(uint16_t));
buf += KEY_HSIZE;
/* Write lowercased name */
int ret = knot_dname_to_wire(buf, name, KNOT_DNAME_MAXLEN);
if (ret <= 0) {
/* Convert to lookup format */
int ret = knot_dname_lf(buf, name, NULL);
if (ret != 0) {
return 0;
}
knot_dname_to_lower(buf);
return KEY_HSIZE + ret;
/* Write tag + type */
uint8_t name_len = buf[0];
buf[0] = tag;
memcpy(buf + sizeof(uint8_t) + name_len, &rrtype, sizeof(uint16_t));
return name_len + KEY_HSIZE;
}
static struct kr_cache_entry *cache_entry(struct kr_cache_txn *txn, uint8_t tag, const knot_dname_t *name, uint16_t type)
......@@ -333,7 +372,7 @@ int kr_cache_peek_rrsig(struct kr_cache_txn *txn, knot_rrset_t *rr, uint32_t *ti
/* Check if the RRSet is in the cache. */
struct kr_cache_entry *entry = NULL;
int ret = kr_cache_peek(txn, KR_CACHE_RRSIG, rr->owner, rr->type, &entry, timestamp);
int ret = kr_cache_peek(txn, KR_CACHE_SIG, rr->owner, rr->type, &entry, timestamp);
if (ret != 0) {
return ret;
}
......@@ -368,5 +407,5 @@ int kr_cache_insert_rrsig(struct kr_cache_txn *txn, const knot_rrset_t *rr, uint
}
namedb_val_t data = { rr->rrs.data, knot_rdataset_size(&rr->rrs) };
return kr_cache_insert(txn, KR_CACHE_RRSIG, rr->owner, typec, &header, data);
return kr_cache_insert(txn, KR_CACHE_SIG, rr->owner, typec, &header, data);
}
......@@ -24,7 +24,7 @@ enum kr_cache_tag {
KR_CACHE_RR = 'R',
KR_CACHE_PKT = 'P',
KR_CACHE_SEC = 'S',
KR_CACHE_RRSIG = 'G',
KR_CACHE_SIG = 'G',
KR_CACHE_USER = 0x80
};
......@@ -36,6 +36,7 @@ struct kr_cache_entry
uint32_t timestamp;
uint32_t ttl;
uint16_t count;
uint16_t rank;
uint8_t data[];
};
......
......@@ -169,13 +169,15 @@ static int stash(knot_layer_t *ctx, knot_pkt_t *pkt)
if (!(qry->flags & QUERY_RESOLVED) || qry->flags & QUERY_CACHED || ctx->state & KNOT_STATE_FAIL) {
return ctx->state; /* Don't cache anything if failed. */
}
bool is_auth = knot_wire_get_aa(pkt->wire);
int pkt_class = kr_response_classify(pkt);
if (!(pkt_class & (PKT_NODATA|PKT_NXDOMAIN)) && is_auth) {
return ctx->state; /* Cache only negative, not-cached answers. */
/* Cache only authoritative answers from IN class. */
if (!knot_wire_get_aa(pkt->wire) || knot_pkt_qclass(pkt) != KNOT_CLASS_IN) {
return ctx->state;
}
if (knot_pkt_qclass(pkt) != KNOT_CLASS_IN) {
return ctx->state; /* Only IN class */
const bool is_any = knot_pkt_qtype(pkt) == KNOT_RRTYPE_ANY;
int pkt_class = kr_response_classify(pkt);
/* Cache only NODATA/NXDOMAIN or ANY answers. */
if (!(pkt_class & (PKT_NODATA|PKT_NXDOMAIN)) || is_any) {
return ctx->state;
}
uint32_t ttl = packet_ttl(pkt);
if (ttl == 0) {
......
......@@ -47,7 +47,7 @@ static int loot_rr(struct kr_cache_txn *txn, knot_pkt_t *pkt, const knot_dname_t
if (fetch_rrsig) {
ret = kr_cache_peek_rrsig(txn, &cache_rr, &drift);
} else {
ret = kr_cache_peek_rr(txn, &cache_rr, &drift);
ret = kr_cache_peek_rr(txn, &cache_rr, &drift);
}
if (ret != 0) {
return ret;
......
......@@ -3,6 +3,24 @@ Cache control
Module providing an interface to cache database, for inspection, manipulation and purging.
Example
^^^^^^^
.. code-block:: lua
-- Query cache for 'domain.cz'
cachectl['domain.cz']
-- Query cache for all records at/below 'insecure.net'
cachectl['*.insecure.net']
-- Clear 'bad.cz' records
cachectl.clear('bad.cz')
-- Clear records at/below 'bad.cz'
cachectl.clear('*.bad.cz')
-- Clear packet cache
cachectl.clear('*. P')
-- Clear whole cache
cachectl.clear()
Properties
^^^^^^^^^^
......@@ -13,8 +31,23 @@ Properties
Prune expired/invalid records.
.. function:: cachectl.clear()
.. function:: cachectl.get([domain])
:return: list of matching records in cache
Fetches matching records from cache. The **domain** can either be:
- a domain name (e.g. ``"domain.cz"``)
- a wildcard (e.g. ``"*.domain.cz"``)
The domain name fetches all records matching this name, while the wildcard matches all records at or below that name.
You can also use a special namespace ``"P"`` to purge NODATA/NXDOMAIN matching this name (e.g. ``"domain.cz P"``).
.. note:: This is equivalent to ``cachectl['domain']`` getter.
.. function:: cachectl.clear([domain])
:return: ``{ result: bool }``
:return: ``bool``
Clear all cache records.
Purge cache records. If the domain isn't provided, whole cache is purged. See *cachectl.get()* documentation for subtree matching policy.
......@@ -26,6 +26,9 @@
*/
#include <time.h>
#include <libknot/descriptor.h>
#include <ccan/json/json.h>
#include <ccan/asprintf/asprintf.h>
#include "daemon/engine.h"
#include "lib/module.h"
......@@ -38,12 +41,95 @@
* Properties.
*/
typedef int (*cache_cb_t)(struct kr_cache_txn *txn, namedb_iter_t *it, namedb_val_t *key, void *baton);
/** @internal Prefix walk. */
NON_NULL(1,2) static int cache_prefixed(struct engine *engine, const char *args, unsigned txn_flags, cache_cb_t cb, void *baton)
{
/* Decode parameters */
uint8_t namespace = 'R';
char *extra = (char *)strchr(args, ' ');
if (extra != NULL) {
extra[0] = '\0';
namespace = extra[1];
}
/* Convert to domain name */
uint8_t buf[KNOT_DNAME_MAXLEN];
if (!knot_dname_from_str(buf, args, sizeof(buf))) {
return kr_error(EINVAL);
}
/* '*' starts subtree search */
const uint8_t *dname = buf;
bool subtree_match = false;
if (dname[0] == '\1' && dname[1] == '*') {
subtree_match = true;
dname = knot_wire_next_label(dname, NULL);
}
/* Convert to search key prefix */
uint8_t prefix[sizeof(uint8_t) + KNOT_DNAME_MAXLEN];
int ret = knot_dname_lf(prefix, dname, NULL);
if (ret != 0) {
return kr_error(EINVAL);
}
size_t prefix_len = prefix[0] + sizeof(uint8_t);
prefix[0] = namespace;
/* Start search transaction */
struct kr_cache *cache = &engine->resolver.cache;
const namedb_api_t *api = cache->api;
struct kr_cache_txn txn;
ret = kr_cache_txn_begin(cache, &txn, txn_flags);
if (ret != 0) {
return kr_error(EIO);
}
/* Walk through cache records matching given prefix.
* Note that since the backend of the cache is opaque, there's no exactly efficient
* way to do prefix search (i.e. Redis uses hashtable but offers SCAN, LMDB can do lexical closest match, ...). */
namedb_val_t key = { prefix, prefix_len };
namedb_iter_t *it = api->iter_begin(&txn.t, 0);
if (it) { /* Seek first key matching the prefix. */
it = api->iter_seek(it, &key, NAMEDB_GEQ);
}
while (it != NULL) {
if (api->iter_key(it, &key) != 0) {
break;
}
/* If not subtree match, allow only keys with the same length. */
if (!subtree_match && key.len != prefix_len + sizeof(uint16_t)) {
break;
}
/* Allow equal or longer keys with the same prefix. */
if (key.len < prefix_len || memcmp(key.data, prefix, prefix_len) != 0) {
break;
}
/* Callback */
ret = cb(&txn, it, &key, baton);
if (ret != 0) {
break;
}
/* Next key */
it = api->iter_next(it);
}
api->iter_finish(it);
kr_cache_txn_commit(&txn);
return ret;
}
/** Return boolean true if a record is expired. */
static bool is_expired(struct kr_cache_entry *entry, uint32_t drift)
{
return drift > entry->ttl;
}
/** @internal Delete iterated key. */
static int cache_delete_cb(struct kr_cache_txn *txn, namedb_iter_t *it, namedb_val_t *key, void *baton)
{
struct kr_cache *cache = txn->owner;
return cache->api->del(&txn->t, key);
}
/**
* Prune expired/invalid records.
*
......@@ -120,6 +206,15 @@ static char* clear(void *env, struct kr_module *module, const char *args)
{
struct engine *engine = env;
/* Partial clear (potentially slow/unsupported). */
if (args && strlen(args) > 0) {
int ret = cache_prefixed(env, args, 0, &cache_delete_cb, NULL);
if (ret != 0) {
return afmt("%s", kr_strerror(ret));
}
return strdup("true");
}
struct kr_cache_txn txn;
int ret = kr_cache_txn_begin(&engine->resolver.cache, &txn, 0);
if (ret != 0) {
......@@ -134,8 +229,71 @@ static char* clear(void *env, struct kr_module *module, const char *args)
kr_cache_txn_abort(&txn);
}
/* Clear reputation tables */
lru_deinit(engine->resolver.cache_rtt);
lru_deinit(engine->resolver.cache_rep);
lru_init(engine->resolver.cache_rtt, LRU_RTT_SIZE);
lru_init(engine->resolver.cache_rep, LRU_REP_SIZE);
return afmt("%s", ret == 0 ? "true" : kr_strerror(ret));
}
/** @internal Serialize cached record name into JSON. */
static int cache_dump_cb(struct kr_cache_txn *txn, namedb_iter_t *it, namedb_val_t *key, void *baton)
{
JsonNode* json_records = baton;
char buf[KNOT_DNAME_MAXLEN];
/* Extract type */
uint16_t type = 0;
const char *endp = (const char *)key->data + key->len - sizeof(uint16_t);
memcpy(&type, endp, sizeof(uint16_t));
endp -= 1;
/* Extract domain name */
char *dst = buf;
const char *scan = endp - 1;
while (scan > key->data) {
if (*scan == '\0') {
const size_t lblen = endp - scan - 1;
memcpy(dst, scan + 1, lblen);
dst += lblen;
*dst++ = '.';
endp = scan;
}
--scan;
}
memcpy(dst, scan + 1, endp - scan);
JsonNode *json_item = json_find_member(json_records, buf);
if (!json_item) {
json_item = json_mkarray();
json_append_member(json_records, buf, json_item);
}
knot_rrtype_to_string(type, buf, sizeof(buf));
json_append_element(json_item, json_mkstring(buf));
return kr_ok();
}
/**
* Query cached records.
*
* Input: [string] domain name
* Output: { result: bool }
*
*/
static char* get(void *env, struct kr_module *module, const char *args)
{
if (!args) {
return NULL;
}
/* Dump all keys matching prefix */
char *result = NULL;
asprintf(&result, "{ \"result\": %s }", ret == 0 ? "true" : "false");
JsonNode *json_records = json_mkobject();
if (json_records) {
int ret = cache_prefixed(env, args, NAMEDB_RDONLY, &cache_dump_cb, json_records);
if (ret == 0) {
result = json_encode(json_records);
}
json_delete(json_records);
}
return result;
}
......@@ -146,8 +304,9 @@ static char* clear(void *env, struct kr_module *module, const char *args)
struct kr_prop *cachectl_props(void)
{
static struct kr_prop prop_list[] = {
{ &prune, "prune", "Prune expired/invalid records.", },
{ &clear, "clear", "Clear all cache records.", },
{ &prune, "prune", "Prune expired/invalid records." },
{ &clear, "clear", "Clear cache records." },
{ &get, "get", "Get a list of cached record(s)." },
{ NULL, NULL, NULL }
};
return prop_list;
......
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