Newer
Older
/* Copyright (C) 2014 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
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include "contrib/cleanup.h"
#include "lib/cdb_lmdb.h"
#include "lib/cookies/cache.h"
#include "lib/cookies/control.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 void *sockaddr)
{
assert(buf && sockaddr);
const uint8_t *addr = NULL;
size_t addr_len = 0;
if (kr_ok() != knot_sockaddr_bytes(sockaddr, &addr, &addr_len)) {
return 0;
}
assert(addr_len > 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,
if (!cache || !sockaddr) {
return NULL;
}
uint8_t keybuf[KEY_SIZE];
size_t key_len = cache_key(keybuf, tag, sockaddr);
/* 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) {
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 void *sockaddr, struct kr_cache_entry **entry,
uint32_t *timestamp)
if (!cache_isvalid(cache) || !sockaddr || !entry) {
struct kr_cache_entry *found = lookup(cache, tag, sockaddr);
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;
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));
memcpy(dst->data, data.data, data.len);
int kr_cookie_cache_insert(struct kr_cache *cache,
uint8_t tag, const void *sockaddr,
struct kr_cache_entry *header, knot_db_val_t data)
{
if (!cache_isvalid(cache) || !sockaddr || !header) {
return kr_error(EINVAL);
}
/* Insert key */
uint8_t keybuf[KEY_SIZE];
size_t key_len = cache_key(keybuf, tag, sockaddr);
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);
int kr_cookie_cache_remove(struct kr_cache *cache,
uint8_t tag, const void *sockaddr)
{
if (!cache_isvalid(cache) || !sockaddr) {
return kr_error(EINVAL);
}
uint8_t keybuf[KEY_SIZE];
size_t key_len = cache_key(keybuf, tag, sockaddr);
if (key_len == 0) {
return kr_error(EILSEQ);
}
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 void *sockaddr,
struct timed_cookie *cookie, uint32_t *timestamp)
if (!cache_isvalid(cache) || !sockaddr || !cookie || !timestamp) {
return kr_error(EINVAL);
}
/* Check if the RRSet is in the cache. */
struct kr_cache_entry *entry = NULL;
int ret = kr_cookie_cache_peek(cache, KR_CACHE_COOKIE, sockaddr,
&entry, timestamp);
cookie->ttl = entry->ttl;
cookie->cookie_opt = entry->data;
int kr_cookie_cache_insert_cookie(struct kr_cache *cache, const void *sockaddr,
const struct timed_cookie *cookie,
uint32_t timestamp)
if (!cache_isvalid(cache) || !sockaddr) {
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,
.rank = KR_RANK_BAD,
.flags = KR_CACHE_FLAG_NONE,
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, sockaddr,
&header, data);