Skip to content
Snippets Groups Projects
Commit 871d1d0a authored by Karel Slaný's avatar Karel Slaný Committed by Ondřej Surý
Browse files

Support for cookie options caching.

parent 6ad5fc91
Branches
Tags
1 merge request!38DNS Cookies
/* 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 <libknot/db/db_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)
#define txn_api(txn) ((txn)->owner->api)
#define txn_is_valid(txn) ((txn) && (txn)->owner && txn_api(txn))
/**
* @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() != kr_address_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_txn *txn, uint8_t tag,
const void *sockaddr)
{
if (!txn_is_valid(txn) || !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 = txn_api(txn)->find(&txn->t, &key, &val, 0);
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_txn *txn, uint8_t tag, const void *sockaddr,
struct kr_cache_entry **entry, uint32_t *timestamp)
{
if (!txn_is_valid(txn) || !sockaddr || !entry) {
return kr_error(EINVAL);
}
struct kr_cache_entry *found = lookup(txn, tag, sockaddr);
if (!found) {
txn->owner->stats.miss += 1;
return kr_error(ENOENT);
}
/* Check entry lifetime */
*entry = found;
int ret = check_lifetime(found, timestamp);
if (ret == 0) {
txn->owner->stats.hit += 1;
} else {
txn->owner->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);
}
int kr_cookie_cache_insert(struct kr_cache_txn *txn,
uint8_t tag, const void *sockaddr,
struct kr_cache_entry *header, knot_db_val_t data)
{
if (!txn_is_valid(txn) || !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);
}
knot_db_val_t key = { keybuf, key_len };
knot_db_val_t entry = { NULL, sizeof(*header) + data.len };
const knot_db_api_t *db_api = txn_api(txn);
/* LMDB can do late write and avoid copy */
txn->owner->stats.insert += 1;
if (db_api == knot_db_lmdb_api()) {
int ret = db_api->insert(&txn->t, &key, &entry, 0);
if (ret != 0) {
return ret;
}
entry_write(entry.data, header, data);
} else {
/* Other backends must prepare contiguous data first */
entry.data = malloc(entry.len);
if (!entry.data) {
return kr_error(ENOMEM);
}
entry_write(entry.data, header, data);
int ret = db_api->insert(&txn->t, &key, &entry, 0);
free(entry.data);
if (ret != 0) {
return ret;
}
}
return kr_ok();
}
int kr_cookie_cache_peek_cookie(struct kr_cache_txn *txn, const void *sockaddr,
const uint8_t **cookie_opt, uint32_t *timestamp)
{
if (!txn_is_valid(txn) || !sockaddr || !cookie_opt || !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(txn, KR_CACHE_COOKIE, sockaddr, &entry, timestamp);
if (ret != 0) {
return ret;
}
*cookie_opt = entry->data;
return kr_ok();
}
int kr_cookie_cache_insert_cookie(struct kr_cache_txn *txn, const void *sockaddr,
uint8_t *cookie_opt, uint32_t timestamp)
{
if (!txn_is_valid(txn) || !sockaddr) {
return kr_error(EINVAL);
}
/* Ignore empty cookie data. */
if (!cookie_opt) {
return kr_ok();
}
/* Prepare header to write. */
struct kr_cache_entry header = {
.timestamp = timestamp,
.ttl = 72000,
.rank = KR_RANK_BAD,
.flags = KR_CACHE_FLAG_NONE,
.count = 1
};
size_t cookie_opt_size = knot_edns_opt_get_length(cookie_opt) + KNOT_EDNS_OPTION_HDRLEN;
knot_db_val_t data = { cookie_opt, cookie_opt_size };
return kr_cookie_cache_insert(txn, KR_CACHE_COOKIE, sockaddr, &header,
data);
}
/* 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
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/>.
*/
#pragma once
#include "lib/cache.h"
#define KR_CACHE_COOKIE (KR_CACHE_USER + 'C')
KR_EXPORT
int kr_cookie_cache_peek(struct kr_cache_txn *txn, uint8_t tag, const void *sockaddr,
struct kr_cache_entry **entry, uint32_t *timestamp);
KR_EXPORT
int kr_cookie_cache_insert(struct kr_cache_txn *txn,
uint8_t tag, const void *sockaddr,
struct kr_cache_entry *header, knot_db_val_t data);
KR_EXPORT
int kr_cookie_cache_peek_cookie(struct kr_cache_txn *txn, const void *sockaddr,
const uint8_t **cookie_opt, uint32_t *timestamp);
/**
* Insert a DNS cookie (client and server) entry for the given server signature (IP address).
* @param txn transaction instance
* @param sockaddr server IP address
* @param cookie_opt whole EDNS cookie option (header, client and server)
* @param cookie_size size of the cookie
* @param timestamp current time
* @return 0 or an errcode
*/
KR_EXPORT
int kr_cookie_cache_insert_cookie(struct kr_cache_txn *txn, const void *sockaddr,
uint8_t *cookie_opt, uint32_t timestamp);
......@@ -19,9 +19,11 @@
#include <netinet/in.h>
#include <assert.h>
#include <stdint.h>
#include <string.h>
#include <libknot/error.h>
#include "contrib/fnv/fnv.h"
#include "lib/cookies/cache.h"
#include "lib/cookies/control.h"
#include "lib/layer.h"
#include "lib/utils.h"
......@@ -30,14 +32,14 @@
static uint8_t cc[KNOT_OPT_COOKIE_CLNT] = { 1, 2, 3, 4, 5, 6, 7, 8};
static struct secret_quantity client = {
static struct secret_quantity client_secret = {
.size = KNOT_OPT_COOKIE_CLNT,
.data = cc
};
struct cookies_control kr_cookies_control = {
.enabled = true,
.client = &client
.secret = &client_secret
};
static int opt_rr_add_cookies(knot_rrset_t *opt_rr,
......@@ -45,14 +47,13 @@ static int opt_rr_add_cookies(knot_rrset_t *opt_rr,
uint8_t *sc, uint16_t sc_len,
knot_mm_t *mm)
{
int ret;
uint16_t cookies_size = 0;
uint8_t *cookies_data = NULL;
cookies_size = knot_edns_opt_cookie_data_len(sc_len);
ret = knot_edns_reserve_option(opt_rr, KNOT_EDNS_OPTION_COOKIE,
cookies_size, &cookies_data, mm);
int ret = knot_edns_reserve_option(opt_rr, KNOT_EDNS_OPTION_COOKIE,
cookies_size, &cookies_data, mm);
if (ret != KNOT_EOK) {
return ret;
}
......@@ -69,8 +70,33 @@ static int opt_rr_add_cookies(knot_rrset_t *opt_rr,
return KNOT_EOK;
}
static void obtain_address(void *sockaddr, uint8_t **addr, size_t *len)
static int opt_rr_add_option(knot_rrset_t *opt_rr, uint8_t *option,
knot_mm_t *mm)
{
assert(opt_rr && option);
uint8_t *reserved_data = NULL;
uint16_t opt_code = knot_edns_opt_get_code(option);
uint16_t opt_len = knot_edns_opt_get_length(option);
uint8_t *opt_data = knot_edns_opt_get_data(option);
int ret = knot_edns_reserve_option(opt_rr, opt_code,
opt_len, &reserved_data, mm);
if (ret != KNOT_EOK) {
return ret;
}
assert(reserved_data);
memcpy(reserved_data, opt_data, opt_len);
return KNOT_EOK;
}
int kr_address_bytes(const void *sockaddr, const uint8_t **addr, size_t *len)
{
if (!sockaddr || !addr || !len) {
return kr_error(EINVAL);
}
assert(sockaddr && addr && len);
int addr_family = ((struct sockaddr *) sockaddr)->sa_family;
......@@ -89,7 +115,7 @@ static void obtain_address(void *sockaddr, uint8_t **addr, size_t *len)
*len = 0;
addr_family = AF_UNSPEC;
DEBUG_MSG(NULL, "%s\n", "could obtain IP address");
return;
return kr_error(EINVAL);
break;
}
......@@ -98,6 +124,8 @@ static void obtain_address(void *sockaddr, uint8_t **addr, size_t *len)
inet_ntop(addr_family, *addr, ns_str, sizeof(ns_str));
DEBUG_MSG(NULL, "obtaned IP address '%s'\n", ns_str);
}
return kr_ok();
}
int kr_client_cokie_fnv64(uint8_t cc_buf[KNOT_OPT_COOKIE_CLNT],
......@@ -113,7 +141,7 @@ int kr_client_cokie_fnv64(uint8_t cc_buf[KNOT_OPT_COOKIE_CLNT],
return kr_error(EINVAL);
}
uint8_t *addr = NULL;
const uint8_t *addr = NULL;
size_t size = 0;
Fnv64_t hash_val = FNV1A_64_INIT;
......@@ -121,23 +149,23 @@ int kr_client_cokie_fnv64(uint8_t cc_buf[KNOT_OPT_COOKIE_CLNT],
/* Client address currently always ignored. */
#if 0
if (clnt_sockaddr) {
obtain_address(clnt_sockaddr, &addr, &size);
if (addr && size) {
if (kr_ok() == kr_address_bytes(clnt_sockaddr, &addr, &size)) {
assert(addr && size);
hash_val = fnv_64a_buf(addr, size, hash_val);
}
}
#endif
if (srvr_sockaddr) {
obtain_address(srvr_sockaddr, &addr, &size);
if (addr && size) {
hash_val = fnv_64a_buf(addr, size, hash_val);
if (kr_ok() == kr_address_bytes(srvr_sockaddr, &addr, &size)) {
assert(addr && size);
hash_val = fnv_64a_buf((void *) addr, size, hash_val);
}
}
if (secret && secret->size && secret->data) {
DEBUG_MSG(NULL, "%s\n", "adding client secret into cookie");
hash_val = fnv_64a_buf(addr, size, hash_val);
hash_val = fnv_64a_buf((void *) addr, size, hash_val);
}
assert(KNOT_OPT_COOKIE_CLNT == sizeof(hash_val));
......@@ -153,18 +181,23 @@ int kr_request_put_cookie(struct cookies_control *cntrl, void *clnt_sockaddr,
assert(cntrl);
assert(pkt);
uint8_t cc[KNOT_OPT_COOKIE_CLNT];
if (!pkt->opt_rr) {
return kr_ok();
}
if (!cntrl->client) {
if (!cntrl->secret) {
return kr_error(EINVAL);
}
int ret = kr_client_cokie_fnv64(cc, clnt_sockaddr, srvr_sockaddr,
cntrl->client);
//
struct kr_cache_txn txn;
const uint8_t *cached_cookie = NULL;
uint32_t timestamp = 0;
kr_cache_txn_begin(&kr_cookies_control.cache, &txn, KNOT_DB_RDONLY);
int ret = kr_cookie_cache_peek_cookie(&txn, srvr_sockaddr,
&cached_cookie, &timestamp);
bool cached = (ret == kr_ok());
//
/* This is a very nasty hack that prevents the packet to be corrupted
* when using contemporary 'Cookie interface'. */
......@@ -181,9 +214,21 @@ int kr_request_put_cookie(struct cookies_control *cntrl, void *clnt_sockaddr,
}
#endif
/* TODO -- generate client cookie from client address, server address
* and secret quantity. */
ret = opt_rr_add_cookies(pkt->opt_rr, cc, NULL, 0, &pkt->mm);
if (cached) {
ret = opt_rr_add_option(pkt->opt_rr, (uint8_t *) cached_cookie,
&pkt->mm);
} else {
/* Generate new client cookie only. */
uint8_t cc[KNOT_OPT_COOKIE_CLNT];
ret = kr_client_cokie_fnv64(cc, clnt_sockaddr, srvr_sockaddr,
cntrl->secret);
/* TODO -- generate client cookie from client address, server address
* and secret quantity. */
ret = opt_rr_add_cookies(pkt->opt_rr, cc, NULL, 0, &pkt->mm);
}
kr_cache_txn_abort(&txn);
/* Write to packet. */
assert(pkt->current == KNOT_ADDITIONAL);
......
......@@ -34,7 +34,7 @@ struct secret_quantity {
/** DNSSEC cookies controlling structure. */
struct cookies_control {
bool enabled; /*!< Enabled/disables DNS cookies functionality. */
struct secret_quantity *client; /*!< Client secret quantity. */
struct secret_quantity *secret; /*!< Client secret quantity. */
struct kr_cache cache; /*!< Server cookies cache. */
};
......@@ -43,6 +43,14 @@ struct cookies_control {
KR_EXPORT
extern struct cookies_control kr_cookies_control;
/**
* Get pointers to IP address bytes.
* @param sockaddr socket address
* @param addr pointer to address
* @param len address length
*/
int kr_address_bytes(const void *sockaddr, const uint8_t **addr, size_t *len);
/**
* Compute client cookie.
* @not At least one of the arguments must be non-null.
......
......@@ -5,6 +5,7 @@ libkres_SOURCES := \
lib/layer/validate.c \
lib/layer/rrcache.c \
lib/layer/pktcache.c \
lib/cookies/cache.c \
lib/cookies/control.c \
lib/dnssec/nsec.c \
lib/dnssec/nsec3.c \
......@@ -25,6 +26,7 @@ libkres_HEADERS := \
lib/generic/map.h \
lib/generic/set.h \
lib/layer.h \
lib/cookies/cache.h \
lib/cookies/control.h \
lib/dnssec/nsec.h \
lib/dnssec/nsec3.h \
......
......@@ -27,6 +27,7 @@
#include <time.h>
#include "daemon/engine.h"
#include "lib/cookies/cache.h"
#include "lib/cookies/control.h"
#include "lib/module.h"
#include "lib/layer.h"
......@@ -69,6 +70,39 @@ static int check_client_cookie(const uint8_t cc[KNOT_OPT_COOKIE_CLNT],
return kr_error(EINVAL);
}
/**
* Tries to guess the name server address from the reputation mechanism.
*/
static const struct sockaddr *guess_server_addr(const uint8_t cc[KNOT_OPT_COOKIE_CLNT],
struct kr_nsrep *nsrep,
struct secret_quantity *secret)
{
assert(cc && nsrep && secret);
const struct sockaddr *sockaddr = NULL;
/* Abusing name server reputation mechanism to obtain IP addresses. */
for (int i = 0; i < KR_NSREP_MAXADDR; ++i) {
if (nsrep->addr[i].ip.sa_family == AF_UNSPEC) {
break;
}
int ret = check_client_cookie(cc, NULL, &nsrep->addr[i], secret);
WITH_DEBUG {
char addr_str[INET6_ADDRSTRLEN];
inet_ntop(nsrep->addr[i].ip.sa_family,
kr_nsrep_inaddr(nsrep->addr[i]), addr_str,
sizeof(addr_str));
DEBUG_MSG(NULL, "nsrep address '%s' %d\n", addr_str, ret);
}
if (ret == kr_ok()) {
sockaddr = (struct sockaddr *) &nsrep->addr[i];
break;
}
}
return sockaddr;
}
/** Process response. */
static int check_response(knot_layer_t *ctx, knot_pkt_t *pkt)
{
......@@ -104,31 +138,14 @@ static int check_response(knot_layer_t *ctx, knot_pkt_t *pkt)
DEBUG_MSG(NULL, "%s\n", "checking response for received cookies");
const void *srvr_sockaddr = NULL;
const struct sockaddr *srvr_sockaddr = NULL;
struct kr_request *req = ctx->data;
struct kr_query *qry = req->current_query;
struct kr_nsrep *ns = &qry->ns;
/* Abusing name server reputation mechanism to obtain IP addresses. */
for (int i = 0; i < KR_NSREP_MAXADDR; ++i) {
if (ns->addr[i].ip.sa_family == AF_UNSPEC) {
break;
}
ret = check_client_cookie(cc, NULL, &ns->addr[i], kr_cookies_control.client);
WITH_DEBUG {
char addr_str[INET6_ADDRSTRLEN];
inet_ntop(ns->addr[i].ip.sa_family,
kr_nsrep_inaddr(ns->addr[i]), addr_str,
sizeof(addr_str));
DEBUG_MSG(NULL, "nsrep address '%s' %d\n", addr_str, ret);
}
if (ret == kr_ok()) {
srvr_sockaddr = &ns->addr[i];
break;
}
}
srvr_sockaddr = guess_server_addr(cc, ns, kr_cookies_control.secret);
if (!srvr_sockaddr) {
DEBUG_MSG(NULL, "%s\n",
"could not ensure any server for received cookie");
......@@ -136,8 +153,26 @@ static int check_response(knot_layer_t *ctx, knot_pkt_t *pkt)
return ctx->state;
}
struct kr_cache_txn txn;
if (kr_cache_txn_begin(&kr_cookies_control.cache, &txn, 0) != 0) {
/* Could not acquire cache. */
return ctx->state;
}
DEBUG_MSG(NULL, "%s\n", "caching server cookie");
/* TODO -- Cache only missing or change cookie. */
ret = kr_cookie_cache_insert_cookie(&txn, srvr_sockaddr, cookie_opt,
qry->timestamp.tv_sec);
if (ret != kr_ok()) {
kr_cache_txn_abort(&txn);
} else {
DEBUG_MSG(NULL, "%s\n", "cookie_cached");
kr_cache_txn_commit(&txn);
}
print_packet_dflt(pkt);
return ctx->state;
......
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